diff options
author | Jelle Meeus <sogladev@gmail.com> | 2025-05-30 16:42:11 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-30 11:42:11 -0300 |
commit | b15507eb06accb17d30f474dde4aefd38a8a24e5 (patch) | |
tree | d2b47790a4cf25c51f9b2b060dc446921f7fde2f | |
parent | 4ad3df721089c2f67dcdd3ced958d5bfcda8dd7b (diff) |
feat(Scripts/Commands): `.debug boundary` to visualize `CreatureBoundary` (#22099)
Co-authored-by: avarishd <46330494+avarishd@users.noreply.github.com>
Co-authored-by: treeston <treeston.mmoc@gmail.com>
-rw-r--r-- | data/sql/updates/pending_db_world/rev_1746807548193171300.sql | 4 | ||||
-rw-r--r-- | src/server/game/AI/CreatureAI.cpp | 111 | ||||
-rw-r--r-- | src/server/game/AI/CreatureAI.h | 3 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_debug.cpp | 24 |
4 files changed, 141 insertions, 1 deletions
diff --git a/data/sql/updates/pending_db_world/rev_1746807548193171300.sql b/data/sql/updates/pending_db_world/rev_1746807548193171300.sql new file mode 100644 index 0000000000..0c1e24e671 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1746807548193171300.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name`IN('debug boundary'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('debug boundary', 3, 'Syntax: .debug boundary [duration] [fill] [z]\nOptional arguments:\n- duration: Duration in ms (default: 5000, max: 180000).\n- fill: Fills the boundary with markers.\n- z: Includes z-axis in visualization.'); diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 82d8279b3b..d246171e28 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -26,8 +26,10 @@ #include "MapReference.h" #include "Player.h" #include "ScriptMgr.h" +#include "TemporarySummon.h" #include "Vehicle.h" #include "ZoneScript.h" +#include <functional> //Disable CreatureAI when charmed void CreatureAI::OnCharmed(bool /*apply*/) @@ -383,6 +385,115 @@ void CreatureAI::MoveBackwardsChecks() me->GetMotionMaster()->MoveBackwards(victim, moveDist); } +int32 CreatureAI::VisualizeBoundary(uint32 duration, Unit* owner, bool fill, bool checkZ) const +{ + static constexpr float BOUNDARY_STEP = 5.0f; + static constexpr uint32 BOUNDARY_VISUALIZE_CREATURE = 21659; // Floaty Flavor Eye + static constexpr float BOUNDARY_VISUALIZE_CREATURE_SCALE = 0.25f; + static constexpr uint32 BOUNDARY_MAX_SPAWNS = 8000; + static constexpr float BOUNDARY_MAX_DISTANCE = MAX_SEARCHER_DISTANCE; + + float boundaryStep = fill && checkZ ? BOUNDARY_STEP * 2 : BOUNDARY_STEP; + + Position const boundaryDirections[6] = { + {boundaryStep, 0, 0 }, + {-boundaryStep, 0, 0 }, + {0, boundaryStep, 0 }, + {0, -boundaryStep, 0 }, + {0, 0, boundaryStep }, + {0, 0, -boundaryStep} + }; + + if (!owner) + return -1; + + if (!_boundary || _boundary->empty()) + return LANG_CREATURE_MOVEMENT_NOT_BOUNDED; + + Position startPosition = owner->GetPosition(); + if (!IsInBoundary(&startPosition)) // fall back to creature position + { + startPosition = me->GetPosition(); + if (!IsInBoundary(&startPosition)) // fall back to creature home position + { + startPosition = me->GetHomePosition(); + if (!IsInBoundary(&startPosition)) + return LANG_CREATURE_NO_INTERIOR_POINT_FOUND; + } + } + + // Helper to spawn visualization creature + auto spawnVisualizationCreature = [owner, duration, checkZ](Position const& pos) + { + if (TempSummon* summon = + owner->SummonCreature(BOUNDARY_VISUALIZE_CREATURE, pos, TEMPSUMMON_TIMED_DESPAWN, duration)) + { + summon->SetObjectScale(BOUNDARY_VISUALIZE_CREATURE_SCALE); + summon->SetUnitFlag(UNIT_FLAG_STUNNED); + summon->SetImmuneToAll(true); + summon->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE_2); + if (!checkZ) + summon->SetDisableGravity(false); + } + }; + + struct PositionHash + { + std::size_t operator()(Position const& pos) const + { + // Convert to fixed precision coordinates. + // We lose precision here, but we don't care about the exact position + int32 x = int32(pos.m_positionX); + int32 y = int32(pos.m_positionY); + int32 z = int32(pos.m_positionZ); + + return std::hash<int32_t>()(x) ^ std::hash<int32_t>()(y) ^ std::hash<int32_t>()(z); + } + }; + + std::unordered_set<Position, PositionHash> visited; + std::queue<Position> queue; + queue.push(startPosition); + visited.insert(startPosition); + uint8 maxDirections = checkZ ? 6 : 4; + uint32 spawns = 0; + + while (!queue.empty()) + { + Position currentPosition = queue.front(); + queue.pop(); + + for (uint8 i = 0; i < maxDirections; ++i) + { + Position const& direction = boundaryDirections[i]; + Position nextPosition = currentPosition; + nextPosition.RelocateOffset(direction); + + if (startPosition.GetExactDist(&nextPosition) > BOUNDARY_MAX_DISTANCE) + break; + + if (visited.find(nextPosition) != visited.end()) + continue; // already visited + + visited.insert(nextPosition); + + bool isInBoundary = IsInBoundary(&nextPosition); + + if ((isInBoundary && fill) || !isInBoundary) + { + spawnVisualizationCreature(currentPosition); + ++spawns; + if (spawns > BOUNDARY_MAX_SPAWNS) + return LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED; + } + + if (isInBoundary) + queue.push(nextPosition); // continue visiting + } + } + return 0; +} + bool CreatureAI::IsInBoundary(Position const* who) const { if (!_boundary) diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 15aa561f67..4672a87918 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -209,6 +209,9 @@ public: virtual void PetStopAttack() { } + // intended for encounter design/debugging. do not use for other purposes. expensive. + int32 VisualizeBoundary(uint32 duration, Unit* owner = nullptr, bool fill = false, bool checkZ = false) const; + // boundary system methods virtual bool CheckInRoom(); CreatureBoundary const* GetBoundary() const { return _boundary; } diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 3a48e5d33a..bb717e7536 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -105,7 +105,8 @@ public: { "unitstate", HandleDebugUnitStateCommand, SEC_ADMINISTRATOR, Console::No }, { "objectcount", HandleDebugObjectCountCommand, SEC_ADMINISTRATOR, Console::Yes}, { "dummy", HandleDebugDummyCommand, SEC_ADMINISTRATOR, Console::No }, - { "mapdata", HandleDebugMapDataCommand, SEC_ADMINISTRATOR, Console::No } + { "mapdata", HandleDebugMapDataCommand, SEC_ADMINISTRATOR, Console::No }, + { "boundary", HandleDebugBoundaryCommand, SEC_ADMINISTRATOR, Console::No } }; static ChatCommandTable commandTable = { @@ -1388,6 +1389,27 @@ public: handler->PSendSysMessage("Created Cells In Map: {} / {}", map->GetCreatedCellsInMapCount(), TOTAL_NUMBER_OF_CELLS_PER_MAP * TOTAL_NUMBER_OF_CELLS_PER_MAP); return true; } + + static bool HandleDebugBoundaryCommand(ChatHandler* handler, Optional<uint32> durationArg, Optional<EXACT_SEQUENCE("fill")> fill, Optional<EXACT_SEQUENCE("z")> checkZ) + { + Player* player = handler->GetPlayer(); + if (!player) + return false; + + Creature* target = handler->getSelectedCreature(); + if (!target || !target->IsAIEnabled) + return false; + + uint32 duration = durationArg.value_or(5 * IN_MILLISECONDS); + if (duration > 180 * IN_MILLISECONDS) // arbitrary upper limit + duration = 180 * IN_MILLISECONDS; + + int32 errMsg = target->AI()->VisualizeBoundary(duration, player, fill.has_value(), checkZ.has_value()); + if (errMsg > 0) + handler->PSendSysMessage(errMsg); + + return true; + } }; void AddSC_debug_commandscript() |