From 1535334a8eec2b1e297668c6182e6fb20b769831 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sat, 20 Jun 2026 04:08:16 -0700 Subject: [PATCH] feat: testmap improvements fixes #1191 removed the force flag because it would only work to let you softlock your character. tested that taking the lego club door now spawns you at the lego club/ns lego club doors always vs letting you spawn where ever. tested that a testmap no longer spawns you where you last were in a zone and instead spawns you at the spawn point inspect allows you to inspect zoneControl and localCharacter now updated docs with the new info --- dGame/Entity.cpp | 8 ++-- dGame/dUtilities/SlashCommandHandler.cpp | 4 +- .../SlashCommands/DEVGMCommands.cpp | 45 ++++++++++++------- dScripts/BaseConsoleTeleportServer.cpp | 3 ++ docs/Commands.md | 4 +- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index c4265470..3673e497 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -316,15 +316,17 @@ void Entity::Initialize() { controllablePhysics->LoadFromXml(m_Character->GetXMLDoc()); const auto mapID = Game::server->GetZoneID(); + const auto& targetSceneName = m_Character->GetTargetScene(); //If we came from another zone, put us in the starting loc - if (m_Character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603) { // Exception for Moon Base as you tend to spawn on the roof. + // Exception for Moon Base as you tend to spawn on the roof. + // second exception if we have a specified targetScene since that would only be possible in a test map + if (m_Character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603 || !targetSceneName.empty()) { NiPoint3 pos; NiQuaternion rot = QuatUtils::IDENTITY; - const auto& targetSceneName = m_Character->GetTargetScene(); auto* targetScene = Game::entityManager->GetSpawnPointEntity(targetSceneName); - + LOG("args %i (%s) %i", m_Character->HasBeenToWorld(mapID), targetSceneName.c_str(), targetScene != nullptr); if (m_Character->HasBeenToWorld(mapID) && targetSceneName.empty()) { pos = m_Character->GetRespawnPoint(mapID); rot = Game::zoneManager->GetZone()->GetSpawnRot(); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 6c2d96ee..e4782db4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -261,7 +261,7 @@ void SlashCommandHandler::Startup() { Command TestMapCommand{ .help = "Transfers you to the given zone", - .info = "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).", + .info = "Transfers you to the given zone by id and clone id and then spawns you at the specified spawn point if one was specified. Ignores instance-id for now.", .aliases = { "testmap", "tm" }, .handle = DEVGMCommands::TestMap, .requiredLevel = eGameMasterLevel::FORUM_MODERATOR @@ -468,7 +468,7 @@ void SlashCommandHandler::Startup() { Command InspectCommand{ .help = "Inspect an object", - .info = "Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See detailed usage in the DLU docs", + .info = "Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the IDs of all components the entity has. Use `localCharacter` or `zoneControl` to inspect your current character or the zone control object.", .aliases = { "inspect" }, .handle = DEVGMCommands::Inspect, .requiredLevel = eGameMasterLevel::DEVELOPER diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 745798b8..5b464ef0 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1051,7 +1051,8 @@ namespace DEVGMCommands { ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); LWOCLONEID cloneId = 0; - bool force = false; + LWOINSTANCEID instanceID{}; + std::string targetScene; const auto reqZoneOptional = GeneralUtils::TryParse(splitArgs[0]); if (!reqZoneOptional) { @@ -1061,29 +1062,34 @@ namespace DEVGMCommands { const LWOMAPID reqZone = reqZoneOptional.value(); if (splitArgs.size() > 1) { - auto index = 1; - - if (splitArgs[index] == "force") { - index++; - - force = true; + const auto cloneIdOptional = GeneralUtils::TryParse(splitArgs[1]); + if (!cloneIdOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + return; } - if (splitArgs.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse(splitArgs[index]); - if (!cloneIdOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + cloneId = cloneIdOptional.value(); + + if (splitArgs.size() > 2) { + const auto instanceIDVal = GeneralUtils::TryParse(splitArgs[2]); + if (!instanceIDVal) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid instance id."); return; } - cloneId = cloneIdOptional.value(); + + instanceID = instanceIDVal.value(); + } + + if (splitArgs.size() > 3) { + targetScene = splitArgs[3]; } } const auto objid = entity->GetObjectID(); - if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery + if (Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid, targetScene](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { auto* entity = Game::entityManager->GetEntity(objid); if (!entity) return; @@ -1101,6 +1107,7 @@ namespace DEVGMCommands { entity->GetCharacter()->SetZoneID(zoneID); entity->GetCharacter()->SetZoneInstance(zoneInstance); entity->GetCharacter()->SetZoneClone(zoneClone); + entity->GetCharacter()->SetTargetScene(targetScene); entity->GetComponent()->SetLastRocketConfig(u""); } @@ -1513,7 +1520,15 @@ namespace DEVGMCommands { void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; - const auto idParsed = GeneralUtils::TryParse(splitArgs[0]); + std::optional idIntermed; + if (splitArgs[0] == "zoneControl") { + idIntermed = 0x3FFF'FFFFFFFE; + } else if (splitArgs[0] == "localCharacter") { + idIntermed = entity->GetObjectID(); + } else { + idIntermed = GeneralUtils::TryParse(splitArgs[0]); + } + const auto idParsed = idIntermed; // First try to get the object by its ID if provided. // Second try to get the object by player name. diff --git a/dScripts/BaseConsoleTeleportServer.cpp b/dScripts/BaseConsoleTeleportServer.cpp index d172696c..7392f759 100644 --- a/dScripts/BaseConsoleTeleportServer.cpp +++ b/dScripts/BaseConsoleTeleportServer.cpp @@ -94,6 +94,9 @@ void BaseConsoleTeleportServer::TransferPlayer(Entity* self, Entity* player, int const auto& teleportZone = self->GetVar(u"transferZoneID"); + auto* const character = player->GetCharacter(); + if (character && self->HasVar(u"spawnPoint")) character->SetTargetScene(self->GetVarAsString(u"spawnPoint")); + auto* characterComponent = player->GetComponent(); if (characterComponent) characterComponent->SendToZone(GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(teleportZone), 0)); diff --git a/docs/Commands.md b/docs/Commands.md index 702f64cc..2d93adeb 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -122,7 +122,7 @@ These commands are primarily for development and testing. The usage of many of t |leave-zone|`/leave-zone`|If you are in an instanced zone, transfers you to the closest main world. For example, if you are in an instance of Avant Gardens Survival or the Spider Queen Battle, you are sent to Avant Gardens. If you are in the Battle of Nimbus Station, you are sent to Nimbus Station. Aliases: `/leavezone`.|0| |resurrect|`/resurrect`|Resurrects the player.|8| |setminifig|`/setminifig `|Alters your player's minifig. Body part can be one of "Eyebrows", "Eyes", "HairColor", "HairStyle", "Pants", "LeftHand", "Mouth", "RightHand", "Shirt", or "Hands". Changing minifig parts could break the character so this command is limited to GMs.|1| -|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). Aliases: `/tm`.|1| +|testmap|`/testmap (clone-id) (instance-id) (spawn-point)`|Transfers you to the given zone by id and clone id and then spawns you at the specified spawn point if one was specified. Ignores instance-id for now. Aliases: `/tm`.|1| |reportproxphys|`/reportproxphys`|Prints to console the position and radius of proximity sensors.|9| |spawnphysicsverts|`/spawnphysicsverts`|Spawns a 1x1 brick at all vertices of phantom physics objects|8| |teleport|`/teleport (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. Aliases: `/tele`, `/tp`.|6| @@ -145,7 +145,7 @@ These commands are primarily for development and testing. The usage of many of t |getnavmeshheight|`/getnavmeshheight`|Display the navmesh height at your current position|8| |giveuscore|`/giveuscore `|Gives uscore|8| |gmadditem|`/gmadditem (count)`|Adds the given item to your inventory by id. Aliases: `/give`.|8| -|inspect|`/inspect (-m | -a | -s | -p | -f (faction) | -t)`|Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the IDs of all components the entity has. See detailed usage in the DLU docs|8| +|inspect|`/inspect (-m | -a | -s | -p | -f (faction) | -t)`|Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the IDs of all components the entity has. Use `localCharacter` or `zoneControl` to inspect your current character or the zone control object.|8| |list-spawns|`/list-spawns`|Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one. Aliases: `/listspawns`.|8| |locrow|`/locrow`|Prints your current position and rotation information to the console|8| |lookup|`/lookup `|Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.|8|