From fc70efa503c2a8fb878dd3886a88a429499fda1d Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 16 Oct 2026 11:13:19 -0500 Subject: [PATCH] feat: scene point commands take a step and use brick LOTs from the BrickIDTable Co-Authored-By: Claude Opus 5.5 --- dGame/dUtilities/SlashCommandHandler.cpp | 6 +-- .../SlashCommands/DEVGMCommands.cpp | 48 ++++++++++++------- docs/Commands.md | 4 ++ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index fdc40ee56..1d9eda2e0 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -837,7 +837,7 @@ void SlashCommandHandler::Startup() { Command SpawnScenePointsCommand{ .help = "Spawn bricks at points across your current scene", - .info = "Spawns bricks at sampled points across the player's current scene using terrain scene map data.", + .info = "Spawns bricks across the player's current scene using terrain scene map data, one every heightmap points (default 8). Usage: /spawnscenepoints (step)", .aliases = { "spawnscenepoints" }, .handle = DEVGMCommands::SpawnScenePoints, .requiredLevel = eGameMasterLevel::DEVELOPER @@ -845,8 +845,8 @@ void SlashCommandHandler::Startup() { RegisterCommand(SpawnScenePointsCommand); Command SpawnAllScenePointsCommand{ - .help = "Spawn bricks at ALL vertices in ALL scenes (high density, many entities)", - .info = "Spawns bricks at every vertex in the terrain mesh for all scenes in the zone. WARNING: Creates a massive number of entities for maximum accuracy visualization.", + .help = "Spawn bricks across all scenes in the zone", + .info = "Spawns bricks across every scene in the zone, one every heightmap points (default 8; 1 = every point, a massive number of entities). Usage: /spawnallscenepoints (step)", .aliases = { "spawnallscenepoints", "spawnallscenes" }, .handle = DEVGMCommands::SpawnAllScenePoints, .requiredLevel = eGameMasterLevel::DEVELOPER diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index e3f8462f1..ecf9fcecf 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1,5 +1,6 @@ #include "DEVGMCommands.h" +#include #include // Classes @@ -24,6 +25,8 @@ // Database #include "Database.h" +#include "CDClientManager.h" +#include "CDBrickIDTableTable.h" #include "CDObjectsTable.h" #include "CDRewardCodesTable.h" #include "CDLootMatrixTable.h" @@ -1891,6 +1894,19 @@ namespace DEVGMCommands { } } + // A different LEGO brick per scene so the scenes can be told apart, from the BrickIDTable (every brick's LOT) + static LOT GetScenePointLot(uint8_t sceneID) { + static const std::vector brickLots = [] { + std::vector lots; + for (const auto& brick : CDClientManager::GetTable()->Query([](const CDBrickIDTable&) { return true; })) { + lots.push_back(brick.NDObjectID); + } + std::ranges::sort(lots); + return lots; + }(); + return brickLots.empty() ? LOT_NULL : brickLots[sceneID % brickLots.size()]; + } + void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto position = entity->GetPosition(); @@ -1982,8 +1998,9 @@ namespace DEVGMCommands { } void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - // Hardcoded to use LOT 33 - const uint32_t lot = 33; + // Spawn one brick every heightmap points (default 8) + const auto parsedStep = GeneralUtils::TryParse(args); + const uint32_t step = parsedStep && *parsedStep > 0 ? *parsedStep : 8; // Get player's current position and scene const auto position = entity->GetPosition(); @@ -2012,15 +2029,13 @@ namespace DEVGMCommands { for (const auto& chunk : raw.chunks) { if (!chunk.IsValidForSceneLookup()) continue; - for (uint32_t i = 0; i < chunk.width; ++i) { - for (uint32_t j = 0; j < chunk.height; ++j) { - if (i * chunk.width + j >= chunk.heightMap.size()) continue; - + for (uint32_t i = 0; i < chunk.width; i += step) { + for (uint32_t j = 0; j < chunk.height; j += step) { const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j); if (sceneID != currentSceneID.GetSceneID()) continue; EntityInfo info; - info.lot = lot + sceneID; + info.lot = GetScenePointLot(sceneID); info.pos = chunk.GridToWorldPos(i, j); info.rot = QuatUtils::IDENTITY; info.spawner = nullptr; @@ -2048,14 +2063,15 @@ namespace DEVGMCommands { const auto* sceneRef = zone->GetScene(currentSceneID); const std::string sceneName = sceneRef ? sceneRef->name : "Unknown"; std::ostringstream feedback; - feedback << "Spawned " << spawnedCount << " points (LOT " << lot + currentSceneID.GetSceneID() << ") in scene " + feedback << "Spawned " << spawnedCount << " points (LOT " << GetScenePointLot(currentSceneID.GetSceneID()) << ") in scene " << currentSceneID.GetSceneID() << " (" << sceneName << ")."; ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str())); } void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - // Hardcoded to use LOT 33 - const uint32_t lot = 33; + // Spawn one brick every heightmap points (default 8) + const auto parsedStep = GeneralUtils::TryParse(args); + const uint32_t step = parsedStep && *parsedStep > 0 ? *parsedStep : 8; // Get the zone const auto* zone = Game::zoneManager->GetZone(); @@ -2077,15 +2093,13 @@ namespace DEVGMCommands { for (const auto& chunk : raw.chunks) { if (!chunk.IsValidForSceneLookup()) continue; - for (uint32_t i = 0; i < chunk.width; ++i) { - for (uint32_t j = 0; j < chunk.height; ++j) { - if (i * chunk.width + j >= chunk.heightMap.size()) continue; - + for (uint32_t i = 0; i < chunk.width; i += step) { + for (uint32_t j = 0; j < chunk.height; j += step) { const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j); if (sceneID == 0) continue; EntityInfo info; - info.lot = lot + sceneID; + info.lot = GetScenePointLot(sceneID); info.pos = chunk.GridToWorldPos(i, j); info.rot = QuatUtils::IDENTITY; info.spawner = nullptr; @@ -2105,13 +2119,13 @@ namespace DEVGMCommands { // Send detailed feedback std::ostringstream feedback; - feedback << "Spawned " << spawnedCount << " total points (base LOT " << lot << ") across " + feedback << "Spawned " << spawnedCount << " total points across " << sceneSpawnCounts.size() << " scenes:\n"; for (const auto& [sceneID, count] : sceneSpawnCounts) { const auto* sceneRef = zone->GetScene(LWOSCENEID(sceneID)); const std::string sceneName = sceneRef ? sceneRef->name : "Unknown"; - feedback << " Scene " << static_cast(sceneID) << ", LOT: " << (lot + sceneID) << " (" << sceneName << "): " << count << " points\n"; + feedback << " Scene " << static_cast(sceneID) << ", LOT: " << GetScenePointLot(sceneID) << " (" << sceneName << "): " << count << " points\n"; } ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str())); diff --git a/docs/Commands.md b/docs/Commands.md index 2d93adeb0..8f5a8690b 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -143,6 +143,10 @@ These commands are primarily for development and testing. The usage of many of t |freecam|`/freecam`|Toggles freecam mode|8| |freemoney|`/freemoney `|Give yourself coins. Aliases: `/givemoney`, `/money`, `/givecoins`, `/coins`.|8| |getnavmeshheight|`/getnavmeshheight`|Display the navmesh height at your current position|8| +|getscene|`/getscene`|Display the scene ID and name at your current position (alias `/scene`)|8| +|getadjacentscenes|`/getadjacentscenes`|List the scenes connected to your current scene by scene transitions (alias `/adjacentscenes`)|8| +|spawnscenepoints|`/spawnscenepoints (step)`|Spawns a brick every `step` terrain points (default 8) across your current scene|8| +|spawnallscenepoints|`/spawnallscenepoints (step)`|Spawns a brick every `step` terrain points (default 8) across every scene, one brick type per scene (alias `/spawnallscenes`)|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. Use `localCharacter` or `zoneControl` to inspect your current character or the zone control object.|8|