mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
feat(Core/Debug): Trace movement entry points and visualize travel nodes
This commit is contained in:
parent
3269d1a4b3
commit
806013a4c9
10
README.md
10
README.md
@ -48,6 +48,16 @@ Then build the server following the platform-specific instructions in our **[Ins
|
||||
|
||||
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
|
||||
|
||||
### Required server configuration
|
||||
|
||||
In `worldserver.conf` (AzerothCore core config), set:
|
||||
|
||||
```ini
|
||||
PreloadAllNonInstancedMapGrids = 1
|
||||
```
|
||||
|
||||
This is required for `mod-playerbots`.
|
||||
|
||||
### Detailed Guides
|
||||
|
||||
| Guide | Description |
|
||||
|
||||
@ -76,7 +76,7 @@ bool DebugAction::Execute(Event event)
|
||||
return false;
|
||||
|
||||
std::vector<WorldPosition> beginPath, endPath;
|
||||
TravelNodeRoute route = TravelNodeMap::instance().getRoute(botPos, *points.front(), beginPath, bot);
|
||||
TravelNodeRoute route = TravelNodeMap::instance().FindRouteNearestNodes(botPos, *points.front(), beginPath, bot);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Traveling to " << dest->getTitle() << ": ";
|
||||
@ -196,18 +196,18 @@ bool DebugAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition pos(bot);
|
||||
|
||||
std::string const name = "USER:" + text.substr(9);
|
||||
std::string suffix = text.size() > 9 ? text.substr(9) : pos.getAreaName();
|
||||
std::string const name = "USER:" + suffix;
|
||||
|
||||
/* TravelNode* startNode = */ TravelNodeMap::instance().addNode(pos, name, false, false); // startNode not used, but addNode as side effect, fragment marked for removal.
|
||||
{
|
||||
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
|
||||
TravelNodeMap::instance().addNode(pos, name, false, true);
|
||||
|
||||
for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000))
|
||||
{
|
||||
endNode->setLinked(false);
|
||||
}
|
||||
|
||||
botAI->TellMasterNoFacing("Node " + name + " created.");
|
||||
|
||||
TravelNodeMap::instance().setHasToGen();
|
||||
botAI->TellMasterNoFacing("Node " + name + " created. Use console command '.playerbots travel generatenode' to connect nodes.");
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -223,14 +223,15 @@ bool DebugAction::Execute(Event event)
|
||||
if (startNode->isImportant())
|
||||
{
|
||||
botAI->TellMasterNoFacing("Node can not be removed.");
|
||||
return true;
|
||||
}
|
||||
|
||||
TravelNodeMap::instance().m_nMapMtx.lock();
|
||||
{
|
||||
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
|
||||
TravelNodeMap::instance().removeNode(startNode);
|
||||
botAI->TellMasterNoFacing("Node removed.");
|
||||
TravelNodeMap::instance().m_nMapMtx.unlock();
|
||||
}
|
||||
|
||||
TravelNodeMap::instance().setHasToGen();
|
||||
botAI->TellMasterNoFacing("Node removed. Use console command '.playerbots travel generatenode' to finalize nodes.");
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -247,15 +248,17 @@ bool DebugAction::Execute(Event event)
|
||||
node->removeLinkTo(path.first, true);
|
||||
return true;
|
||||
}
|
||||
else if (text.find("gen node") != std::string::npos)
|
||||
else if (text.find("gen node") != std::string::npos ||
|
||||
text.find("gen path") != std::string::npos)
|
||||
{
|
||||
// Pathfinder
|
||||
TravelNodeMap::instance().generateNodes();
|
||||
return true;
|
||||
}
|
||||
else if (text.find("gen path") != std::string::npos)
|
||||
{
|
||||
TravelNodeMap::instance().generatePaths();
|
||||
// Disabled: generateAll() touches Map / grid / mmap state that is only
|
||||
// safe to mutate on the world thread. Running it from a detached worker
|
||||
// (or from a bot tick on a MapUpdater thread) races with world updates
|
||||
// and freezes the server. Use the console command instead, which runs
|
||||
// synchronously on the world thread:
|
||||
// .playerbots travel generatenode
|
||||
botAI->TellMasterNoFacing(
|
||||
"Disabled in chat. Run '.playerbots travel generatenode' from the server console.");
|
||||
return true;
|
||||
}
|
||||
else if (text.find("crop path") != std::string::npos)
|
||||
@ -275,7 +278,7 @@ bool DebugAction::Execute(Event event)
|
||||
[]
|
||||
{
|
||||
TravelNodeMap::instance().removeNodes();
|
||||
TravelNodeMap::instance().loadNodeStore();
|
||||
TravelNodeMap::instance().LoadNodeStore();
|
||||
});
|
||||
|
||||
t.detach();
|
||||
@ -297,7 +300,7 @@ bool DebugAction::Execute(Event event)
|
||||
|
||||
// uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal.
|
||||
|
||||
std::vector<WorldPosition> ppath = l.second->getPath();
|
||||
std::vector<WorldPosition> ppath = l.second->GetPath();
|
||||
|
||||
for (auto p : ppath)
|
||||
{
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "TravelNode.h"
|
||||
|
||||
using namespace Acore::ChatCommands;
|
||||
|
||||
@ -32,6 +33,7 @@ public:
|
||||
{
|
||||
static ChatCommandTable playerbotsDebugCommandTable = {
|
||||
{"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"zone", HandleDebugZoneCommand, SEC_GAMEMASTER, Console::No},
|
||||
};
|
||||
|
||||
static ChatCommandTable playerbotsAccountCommandTable = {
|
||||
@ -41,11 +43,16 @@ public:
|
||||
{"unlink", HandleUnlinkAccountCommand, SEC_PLAYER, Console::No},
|
||||
};
|
||||
|
||||
static ChatCommandTable playerbotsTravelCommandTable = {
|
||||
{"generatenode", HandleGenerateTravelNodesCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
};
|
||||
|
||||
static ChatCommandTable playerbotsCommandTable = {
|
||||
{"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No},
|
||||
{"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"travel", playerbotsTravelCommandTable},
|
||||
{"debug", playerbotsDebugCommandTable},
|
||||
{"account", playerbotsAccountCommandTable},
|
||||
};
|
||||
@ -106,11 +113,177 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleGenerateTravelNodesCommand(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
handler->PSendSysMessage("Regenerating travel node paths...");
|
||||
LOG_INFO("playerbots", "Manual travel node regeneration started via console command.");
|
||||
sTravelNodeMap.generateAll();
|
||||
handler->PSendSysMessage("Travel node regeneration complete. Paths saved to database.");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleDebugBGCommand(ChatHandler* handler, char const* 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)
|
||||
{
|
||||
if (!args || !*args)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user