feat(Core/Debug): Visualize travel nodes and walk paths via .playerbots debug zone showpath

This commit is contained in:
bash 2026-05-01 15:17:53 +02:00
parent 2bb2ededc7
commit 0e0dd961f7
3 changed files with 171 additions and 0 deletions

View File

@ -2454,6 +2454,15 @@ TravelNode* TravelNodeMap::GetNearestNodeInZone(WorldPosition pos, uint32 zoneId
return bestNode; return bestNode;
} }
std::vector<TravelNode*> const& TravelNodeMap::GetNodesInZone(uint32 zoneId) const
{
static std::vector<TravelNode*> const empty;
auto it = m_zoneIndex.find(zoneId);
if (it == m_zoneIndex.end())
return empty;
return it->second;
}
TravelNode* TravelNodeMap::GetNearestNodeOnMap(WorldPosition pos) TravelNode* TravelNodeMap::GetNearestNodeOnMap(WorldPosition pos)
{ {
auto it = m_mapIndex.find(pos.GetMapId()); auto it = m_mapIndex.find(pos.GetMapId());

View File

@ -697,6 +697,10 @@ public:
TravelNode* GetNearestNodeInZone(WorldPosition pos, uint32 zoneId); TravelNode* GetNearestNodeInZone(WorldPosition pos, uint32 zoneId);
TravelNode* GetNearestNodeOnMap(WorldPosition pos); TravelNode* GetNearestNodeOnMap(WorldPosition pos);
// All nodes registered to a zone (post-BuildZoneIndex). Returns an
// empty static vector for unknown zones.
std::vector<TravelNode*> const& GetNodesInZone(uint32 zoneId) const;
bool GetFullPath(TravelPlan& plan, WorldPosition botPos, bool GetFullPath(TravelPlan& plan, WorldPosition botPos,
uint32 botZoneId, WorldPosition destination); uint32 botZoneId, WorldPosition destination);

View File

@ -33,6 +33,7 @@ public:
{ {
static ChatCommandTable playerbotsDebugCommandTable = { static ChatCommandTable playerbotsDebugCommandTable = {
{"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes}, {"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes},
{"zone", HandleDebugZoneCommand, SEC_GAMEMASTER, Console::No},
}; };
static ChatCommandTable playerbotsAccountCommandTable = { static ChatCommandTable playerbotsAccountCommandTable = {
@ -126,6 +127,163 @@ public:
return BGTactics::HandleConsoleCommand(handler, args); return BGTactics::HandleConsoleCommand(handler, args);
} }
// Visual constants for showpath markers. Two waypoint-family
// creatures give nodes vs path waypoints distinct visuals; both
// render at their creature_template default scale (no override).
// nodes (anchors) → 15897, prominent waypoint variant
// path waypoints → 15631, standard BG-showpath waypoint
//
// SHOWPATH_PATH_DISPLAY_ID = 0 uses the path-creature's default
// model. To experiment with a model override, set this to a known-
// good creature display ID for your DB (spell-visual IDs are not
// universally registered as creature displays — using one risks
// summoning invisible markers).
static constexpr uint32 SHOWPATH_NODE_CREATURE = 15897;
static constexpr uint32 SHOWPATH_PATH_CREATURE = 15631;
static constexpr uint32 SHOWPATH_PATH_DISPLAY_ID = 0; // 0 = default model
static constexpr uint32 SHOWPATH_DESPAWN_MS = 60000;
static bool HandleDebugZoneCommand(ChatHandler* handler, char const* args)
{
Player* player = handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr;
if (!player)
{
handler->PSendSysMessage("Command requires an in-game player.");
return false;
}
if (!args || !*args)
{
handler->PSendSysMessage("usage: .playerbots debug zone showpath=all|node|path");
return false;
}
char* cmd = strtok(const_cast<char*>(args), " ");
// showpath=all → nodes + cached path waypoints (full picture)
// showpath=node → only node anchors
// showpath=path → only cached path waypoints (no anchors)
bool showNodes = false;
bool showLinks = false;
if (cmd && strcmp(cmd, "showpath=all") == 0)
{
showNodes = true;
showLinks = true;
}
else if (cmd && strcmp(cmd, "showpath=node") == 0)
{
showNodes = true;
showLinks = false;
}
else if (cmd && strcmp(cmd, "showpath=path") == 0)
{
showNodes = false;
showLinks = true;
}
else
{
handler->PSendSysMessage("usage: .playerbots debug zone showpath=all|node|path");
return false;
}
uint32 zoneId = player->GetZoneId();
std::vector<TravelNode*> const& nodes = sTravelNodeMap.GetNodesInZone(zoneId);
if (nodes.empty())
{
handler->PSendSysMessage("No travel nodes registered in zone {} (is the travel node system loaded?)", zoneId);
return true;
}
// node markers — full-scale anchor at each travel-node position.
uint32 nodesPlaced = 0;
if (showNodes)
{
for (TravelNode* node : nodes)
{
if (!node)
continue;
WorldPosition* pos = node->getPosition();
if (!pos || pos->GetMapId() != player->GetMapId())
continue;
Creature* wp = player->SummonCreature(SHOWPATH_NODE_CREATURE,
pos->GetPositionX(), pos->GetPositionY(),
pos->GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, SHOWPATH_DESPAWN_MS);
if (wp)
{
wp->SetOwnerGUID(player->GetGUID());
++nodesPlaced;
}
}
}
if (!showLinks)
{
handler->PSendSysMessage("Showing {} travel nodes in zone {} (60s)", nodesPlaced, zoneId);
return true;
}
// path-waypoint markers — same creature, scaled down so they
// read as a breadcrumb trail between nodes rather than as more
// anchor points. Walk-type links from any in-zone node are
// drawn; the per-waypoint same-map filter keeps the trail from
// running into other continents. Sparse zones (e.g. Teldrassil)
// would draw nothing if we required dst-in-zone too, since their
// only links go to nodes in neighbouring zones.
constexpr uint32 MAX_PATH_MARKERS = 500;
uint32 pathPlaced = 0;
uint32 linksDrawn = 0;
bool capped = false;
for (TravelNode* node : nodes)
{
if (!node)
continue;
auto* links = node->getLinks();
if (!links)
continue;
for (auto const& kv : *links)
{
TravelNode* dst = kv.first;
TravelNodePath* path = kv.second;
if (!dst || !path)
continue;
if (path->getPathType() != TravelNodePathType::walk)
continue;
++linksDrawn;
for (WorldPosition const& wpPos : path->GetPath())
{
if (wpPos.GetMapId() != player->GetMapId())
continue;
if (pathPlaced >= MAX_PATH_MARKERS)
{
capped = true;
break;
}
Creature* mk = player->SummonCreature(SHOWPATH_PATH_CREATURE,
wpPos.GetPositionX(),
wpPos.GetPositionY(), wpPos.GetPositionZ(),
0, TEMPSUMMON_TIMED_DESPAWN,
SHOWPATH_DESPAWN_MS);
if (mk)
{
mk->SetOwnerGUID(player->GetGUID());
if (SHOWPATH_PATH_DISPLAY_ID)
mk->SetDisplayId(SHOWPATH_PATH_DISPLAY_ID);
++pathPlaced;
}
}
if (capped)
break;
}
if (capped)
break;
}
handler->PSendSysMessage("Showing {} nodes + {} path waypoints across {} walk links in zone {}{} (60s)",
nodesPlaced, pathPlaced, linksDrawn, zoneId,
capped ? " — capped at 500 path markers" : "");
return true;
}
static bool HandleSetSecurityKeyCommand(ChatHandler* handler, char const* args) static bool HandleSetSecurityKeyCommand(ChatHandler* handler, char const* args)
{ {
if (!args || !*args) if (!args || !*args)