summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJelle Meeus <sogladev@gmail.com>2025-05-30 16:42:11 +0200
committerGitHub <noreply@github.com>2025-05-30 11:42:11 -0300
commitb15507eb06accb17d30f474dde4aefd38a8a24e5 (patch)
treed2b47790a4cf25c51f9b2b060dc446921f7fde2f
parent4ad3df721089c2f67dcdd3ced958d5bfcda8dd7b (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.sql4
-rw-r--r--src/server/game/AI/CreatureAI.cpp111
-rw-r--r--src/server/game/AI/CreatureAI.h3
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp24
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()