diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index c0966897..2e93060e 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -287,8 +287,8 @@ void SlashCommandHandler::Startup() { RegisterCommand(SpawnPhysicsVertsCommand); Command TeleportCommand{ - .help = "Teleports you", - .info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z)", + .help = "Teleports you to a position or a player to another player.", + .info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Any of the coordinates can use the syntax of an exact position (10.0), or a relative position (~+10.0). A ~ means use the current value of that axis as the base value. Addition or subtraction is supported (~+10) (~-10). If source player and target player are players that exist in the world, then the source player will be teleported to target player.", .aliases = { "teleport", "tele", "tp" }, .handle = DEVGMCommands::Teleport, .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 37fba911..43f46746 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -555,25 +555,45 @@ namespace DEVGMCommands { } } + std::optional ParseRelativeAxis(const float sourcePos, const std::string& toParse) { + if (toParse.empty()) return std::nullopt; + + // relative offset from current position + if (toParse[0] == '~') { + if (toParse.size() == 1) return sourcePos; + + if (toParse.size() < 3 || !(toParse[1] != '+' || toParse[1] != '-')) return std::nullopt; + + const auto offset = GeneralUtils::TryParse(toParse.substr(2)); + if (!offset.has_value()) return std::nullopt; + + bool isNegative = toParse[1] == '-'; + return isNegative ? sourcePos - offset.value() : sourcePos + offset.value(); + } + + return GeneralUtils::TryParse(toParse); + } + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); + const auto& sourcePos = entity->GetPosition(); NiPoint3 pos{}; + auto* sourceEntity = entity; if (splitArgs.size() == 3) { - - const auto x = GeneralUtils::TryParse(splitArgs.at(0)); + const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]); if (!x) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); return; } - const auto y = GeneralUtils::TryParse(splitArgs.at(1)); + const auto y = ParseRelativeAxis(sourcePos.y, splitArgs[1]); if (!y) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); return; } - const auto z = GeneralUtils::TryParse(splitArgs.at(2)); + const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[2]); if (!z) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); return; @@ -584,32 +604,39 @@ namespace DEVGMCommands { pos.SetZ(z.value()); LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); } else if (splitArgs.size() == 2) { + const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]); + auto* sourcePlayer = PlayerManager::GetPlayer(splitArgs[0]); + if (!x && !sourcePlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid x or source player not found."); + return; + } + if (sourcePlayer) sourceEntity = sourcePlayer; - const auto x = GeneralUtils::TryParse(splitArgs.at(0)); - if (!x) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); + const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[1]); + const auto* const targetPlayer = PlayerManager::GetPlayer(splitArgs[1]); + if (!z && !targetPlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid z or target player not found."); return; } - const auto z = GeneralUtils::TryParse(splitArgs.at(1)); - if (!z) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); + if (x && z) { + pos.SetX(x.value()); + pos.SetY(0.0f); + pos.SetZ(z.value()); + } else if (sourcePlayer && targetPlayer) { + pos = targetPlayer->GetPosition(); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Unable to teleport."); return; } - - pos.SetX(x.value()); - pos.SetY(0.0f); - pos.SetZ(z.value()); - LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); } else { ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport () - if no Y given, will teleport to the height of the terrain (or any physics object)."); } + GameMessages::SendTeleport(sourceEntity->GetObjectID(), pos, sourceEntity->GetRotation(), sourceEntity->GetSystemAddress()); - auto* possessorComponent = entity->GetComponent(); + auto* possessorComponent = sourceEntity->GetComponent(); if (possessorComponent) { auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); diff --git a/docs/Commands.md b/docs/Commands.md index 62607939..222abe34 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -59,7 +59,7 @@ These commands are primarily for development and testing. The usage of many of t |testmap|`/testmap (force) (clone-id)`|Transfers you to the given zone by id and clone id. Add "force" to skip checking if the zone is accessible (this can softlock your character, though, if you e.g. try to teleport to Frostburgh).|1| |reportproxphys|`/reportproxphys`|Prints to console the position and radius of proximity sensors.|6| |spawnphysicsverts|`/spawnphysicsverts`|Spawns a 1x1 brick at all vertices of phantom physics objects.|6| -|teleport|`/teleport (y) ` or
`/tele (y) `|Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Alias: `/tele`.|6| +|teleport|`/teleport (y) ` or
`/tele (y) `|Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Any of the coordinates can use the syntax of an exact position (10.0), or a relative position (~+10.0). A ~ means use the current value of that axis as the base value. Addition or subtraction is supported (~+10) (~-10). If source player and target player are players that exist in the world, then the source player will be teleported to target player. Alias: `/tele`.|6| |activatespawner|`/activatespawner `|Activates spawner by name.|8| |addmission|`/addmission `|Accepts the mission, adding it to your journal.|8| |boost|`/boost (time)`|Adds a passive boost action if you are in a vehicle. If time is given it will end after that amount of time|8|