feat(Core/Travel): Exclude area-trigger, static-portal, teleport-spell path types from PR

This commit is contained in:
bash 2026-05-30 18:57:42 +02:00
parent 8cb54416bf
commit d9a8ac3a2a
3 changed files with 21 additions and 250 deletions

View File

@ -3508,36 +3508,6 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
return true; return true;
} }
case PathNodeType::NODE_PORTAL:
{
// Pair: source (pointIdx) + dest (pointIdx+1)
if (state.stepIdx + 1 >= state.steps.size())
{
state.Reset();
return false;
}
const PathNodePoint& src = state.steps[state.stepIdx];
const PathNodePoint& dst = state.steps[state.stepIdx + 1];
// Already on destination map?
if (bot->GetMapId() == dst.point.GetMapId())
{
state.stepIdx += 2;
return true;
}
// Walk to portal source
float dist = bot->GetExactDist(src.point.GetPositionX(), src.point.GetPositionY(), src.point.GetPositionZ());
if (dist > INTERACTION_DISTANCE)
return MoveTo(src.point.GetMapId(), src.point.GetPositionX(), src.point.GetPositionY(), src.point.GetPositionZ());
// At portal but didn't cross — natural collision missed.
// Abort the plan; stuck-recovery in MoveFarTo will decide
// whether to retry or teleport the bot.
state.Reset();
return false;
}
case PathNodeType::NODE_TRANSPORT: case PathNodeType::NODE_TRANSPORT:
{ {
if (state.stepIdx + 1 >= state.steps.size()) if (state.stepIdx + 1 >= state.steps.size())
@ -3657,92 +3627,6 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
return true; return true;
} }
case PathNodeType::NODE_TELEPORT:
{
// Teleport-spell node: hearthstone (item 6948 → spell 8690)
// or class teleport spells (mage portals, druid teleport).
// entry holds the spell ID; 8690 is the canonical hearthstone.
uint32 spellId = pt.entry;
if (!spellId)
{
state.Reset();
return false;
}
// Can't cast mid-flight or mid-cast; bail and retry next tick.
if (bot->IsInFlight() || bot->IsNonMeleeSpellCast(false))
return true;
if (bot->IsMounted())
bot->Dismount();
botAI->RemoveShapeshift();
// 8690 is Hearthstone — the AI has a dedicated action for it
// that handles cooldown and inventory checks. Other teleport
// spells go through the generic cast path.
bool cast = false;
if (spellId == 8690)
cast = botAI->DoSpecificAction("hearthstone", Event(), true);
else if (bot->HasSpell(spellId) && !bot->HasSpellCooldown(spellId))
cast = botAI->CastSpell(spellId, bot);
if (!cast)
{
state.Reset();
return false;
}
// Cast started — advance past the teleport step; the spell
// will move the bot, next tick picks up from wherever it lands.
state.stepIdx++;
return true;
}
case PathNodeType::NODE_AREA_TRIGGER:
{
// Walk into an area trigger, server handles teleport on entry.
// Pair: trigger (pointIdx) + dest (pointIdx+1).
if (state.stepIdx + 1 >= state.steps.size())
{
state.Reset();
return false;
}
const PathNodePoint& trigger = state.steps[state.stepIdx];
const PathNodePoint& dest = state.steps[state.stepIdx + 1];
// Already on destination map — area trigger fired, advance.
if (bot->GetMapId() == dest.point.GetMapId())
{
state.stepIdx += 2;
return true;
}
// Walk to the trigger; entering its radius teleports us.
float dist = bot->GetExactDist(trigger.point.GetPositionX(),
trigger.point.GetPositionY(),
trigger.point.GetPositionZ());
if (dist > INTERACTION_DISTANCE)
return MoveTo(trigger.point.GetMapId(),
trigger.point.GetPositionX(),
trigger.point.GetPositionY(),
trigger.point.GetPositionZ());
// At trigger but didn't teleport — likely missing required
// quest/key. Abort; stuck-recovery in MoveFarTo decides next.
state.Reset();
return false;
}
case PathNodeType::NODE_FLYING_MOUNT:
{
// Flying-mount node not implemented — abort. The graph
// generator produces these but their execution is
// server-specific; we treat them as unreachable rather
// than papering over with a teleport.
state.Reset();
return false;
}
default: default:
{ {
LOG_ERROR("playerbots", LOG_ERROR("playerbots",

View File

@ -161,18 +161,8 @@ float TravelNodePath::getCost(Player* bot, uint32 cGold)
if (factionAnnoyance > 0) if (factionAnnoyance > 0)
modifier += 0.3 * factionAnnoyance; // For each level the whole path takes 10% longer. modifier += 0.3 * factionAnnoyance; // For each level the whole path takes 10% longer.
} }
if (getPathType() == TravelNodePathType::flyingMount)
{
if (!bot->IsAlive() || bot->GetLevel() < 70 || !bot->CanFly())
return -1.0f;
float flySpeed = bot->GetSpeed(MOVE_FLIGHT);
if (flySpeed < 1.0f)
flySpeed = 20.0f; // 280% base flying speed fallback
return (distance / flySpeed) * modifier;
}
} }
else if (getPathType() == TravelNodePathType::flightPath || getPathType() == TravelNodePathType::flyingMount) else if (getPathType() == TravelNodePathType::flightPath)
return -1.0f; return -1.0f;
if (getPathType() != TravelNodePathType::walk) if (getPathType() != TravelNodePathType::walk)
@ -885,33 +875,18 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
continue; continue;
} }
if (nodePath->getPathType() == TravelNodePathType::portal || if (nodePath->getPathType() == TravelNodePathType::transport)
nodePath->getPathType() == TravelNodePathType::staticPortal) // Teleport to next node.
{ {
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_PORTAL, nodePath->getPathObject()); // Entry point // Emit the transport's full waypoint route, not just board+exit.
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_PORTAL, nodePath->getPathObject()); // Exit point // Intermediate points carry NODE_TRANSPORT type so the executor
// sees consecutive transport waypoints as one block (board at
// first, disembark at last).
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_TRANSPORT, nodePath->getPathObject());
} }
else if (nodePath->getPathType() == TravelNodePathType::transport) // Move onto transport else if (nodePath->getPathType() == TravelNodePathType::flightPath)
{ {
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TRANSPORT, // Full taxi waypoint route; same reasoning as transport.
nodePath->getPathObject()); // Departure point travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TRANSPORT, nodePath->getPathObject()); // Arrival point
}
else if (nodePath->getPathType() == TravelNodePathType::flightPath) // Use the flightpath
{
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_FLIGHTPATH,
nodePath->getPathObject()); // Departure point
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject()); // Arrival point
}
else if (nodePath->getPathType() == TravelNodePathType::teleportSpell)
{
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
}
else if (nodePath->getPathType() == TravelNodePathType::flyingMount)
{
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_FLYING_MOUNT, 0);
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_FLYING_MOUNT, 0);
} }
else else
{ {
@ -921,15 +896,8 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
node != nodes.back()) // Remove the last point since that will also be the start of the next path. node != nodes.back()) // Remove the last point since that will also be the start of the next path.
path.pop_back(); path.pop_back();
if (path.size() > 1 && prevNode->isPortal() &&
nodePath->getPathType() != TravelNodePathType::portal &&
nodePath->getPathType() != TravelNodePathType::staticPortal) // Do not move to the area trigger if we
// don't plan to take the portal.
path.erase(path.begin());
if (path.size() > 1 && prevNode->isTransport() && if (path.size() > 1 && prevNode->isTransport() &&
nodePath->getPathType() != nodePath->getPathType() != TravelNodePathType::transport)
TravelNodePathType::transport) // Do not move to the transport if we aren't going to take it.
path.erase(path.begin()); path.erase(path.begin());
travelPath.addPath(path, PathNodeType::NODE_PATH); travelPath.addPath(path, PathNodeType::NODE_PATH);
@ -1504,73 +1472,6 @@ void TravelNodeMap::generateStartNodes()
} }
} }
void TravelNodeMap::generateAreaTriggerNodes()
{
// Entrance nodes
for (auto const& itr : sObjectMgr->GetAllAreaTriggerTeleports())
{
AreaTriggerTeleport const& atEntry = itr.second;
AreaTrigger const* at = sObjectMgr->GetAreaTrigger(itr.first);
if (!at)
continue;
WorldPosition inPos = WorldPosition(at->map, at->x, at->y, at->z, at->orientation);
WorldPosition outPos = WorldPosition(atEntry.target_mapId, atEntry.target_X, atEntry.target_Y, atEntry.target_Z,
atEntry.target_Orientation);
std::string nodeName;
if (!outPos.isOverworld())
nodeName = outPos.getAreaName(false) + " entrance";
else if (!inPos.isOverworld())
nodeName = inPos.getAreaName(false) + " exit";
else
nodeName = inPos.getAreaName(false) + " portal";
TravelNodeMap::instance().addNode(inPos, nodeName, true, true);
}
// Exit nodes
for (auto const& itr : sObjectMgr->GetAllAreaTriggerTeleports())
{
AreaTriggerTeleport const& atEntry = itr.second;
AreaTrigger const* at = sObjectMgr->GetAreaTrigger(itr.first);
if (!at)
continue;
WorldPosition inPos = WorldPosition(at->map, at->x, at->y, at->z, at->orientation);
WorldPosition outPos = WorldPosition(atEntry.target_mapId, atEntry.target_X, atEntry.target_Y, atEntry.target_Z,
atEntry.target_Orientation);
std::string nodeName;
if (!outPos.isOverworld())
nodeName = outPos.getAreaName(false) + " entrance";
else if (!inPos.isOverworld())
nodeName = inPos.getAreaName(false) + " exit";
else
nodeName = inPos.getAreaName(false) + " portal";
//TravelNode* entryNode = TravelNodeMap::instance().getNode(outPos, nullptr, 20.0f); // Entry side, portal exit. //not used, line marked for removal.
TravelNode* outNode = TravelNodeMap::instance().addNode(outPos, nodeName, true, true); // Exit size, portal exit.
TravelNode* inNode = TravelNodeMap::instance().getNode(inPos, nullptr, 5.0f); // Entry side, portal center.
// Portal link from area trigger to area trigger destination.
if (outNode && inNode)
{
TravelNodePath travelPath(0.1f, 3.0f, (uint8)TravelNodePathType::portal, itr.first, true);
travelPath.setPath({*inNode->getPosition(), *outNode->getPosition()});
inNode->setPathTo(outNode, travelPath);
}
}
}
void TravelNodeMap::generateTransportNodes() void TravelNodeMap::generateTransportNodes()
{ {
for (auto const& itr : *sObjectMgr->GetGameObjectTemplates()) for (auto const& itr : *sObjectMgr->GetGameObjectTemplates())
@ -1674,8 +1575,6 @@ void TravelNodeMap::generateNodes()
generateStartNodes(); generateStartNodes();
LOG_INFO("playerbots", "-Generating npc nodes"); LOG_INFO("playerbots", "-Generating npc nodes");
generateNpcNodes(); generateNpcNodes();
LOG_INFO("playerbots", "-Generating area trigger nodes");
generateAreaTriggerNodes();
LOG_INFO("playerbots", "-Generating transport nodes"); LOG_INFO("playerbots", "-Generating transport nodes");
generateTransportNodes(); generateTransportNodes();
LOG_INFO("playerbots", "-Generating zone mean nodes"); LOG_INFO("playerbots", "-Generating zone mean nodes");

View File

@ -39,12 +39,11 @@
// //
// Edge types (TravelNodePathType): // Edge types (TravelNodePathType):
// walk(1) — Walk via navmesh waypoints (stored in DB) // walk(1) — Walk via navmesh waypoints (stored in DB)
// portal(2) — AreaTrigger teleport (auto-discovered at startup) // areaTrigger(2) — AreaTrigger teleport (auto-discovered at startup)
// transport(3) — Boat/zeppelin (auto-discovered from MO_TRANSPORT) // transport(3) — Boat/zeppelin (auto-discovered from MO_TRANSPORT)
// flightPath(4) — Taxi flight between flight masters // flightPath(4) — Taxi flight between flight masters
// teleportSpell(5) — Spell-based teleport (e.g. mage portals) // teleportSpell(5) — Spell-based teleport (e.g. mage portals)
// staticPortal(6) — Manually defined teleport link (DB only, not pruned by generation) // staticPortal(6) — Manually defined teleport link (DB only, not pruned by generation)
// flyingMount (7) — Use Bots Flying mount to travel (Not currently enabled)
// //
// On server start saved nodes and links are loaded via TravelNodeMap::Init(). An index of nodes by zone is prepared // On server start saved nodes and links are loaded via TravelNodeMap::Init(). An index of nodes by zone is prepared
// (instead of scanning all ~4000 nodes), precomputes connected components for O(1) reachability checks, and builds // (instead of scanning all ~4000 nodes), precomputes connected components for O(1) reachability checks, and builds
@ -91,12 +90,10 @@ enum class TravelNodePathType : uint8
{ {
none = 0, none = 0,
walk = 1, walk = 1,
portal = 2, // values 2 (areaTrigger), 5 (teleportSpell), 6 (staticPortal)
// reserved for future use — generation/execution not yet wired up.
transport = 3, transport = 3,
flightPath = 4, flightPath = 4
teleportSpell = 5,
staticPortal = 6,
flyingMount = 7
}; };
// A connection between two nodes. // A connection between two nodes.
@ -264,15 +261,9 @@ public:
return false; return false;
} }
bool isPortal() // Portal-style link types (areaTrigger, staticPortal) are not
{ // generated in this PR. Stub retained so consumers compile.
for (auto const& link : *getLinks()) bool isPortal() { return false; }
if (link.second->getPathType() == TravelNodePathType::portal ||
link.second->getPathType() == TravelNodePathType::staticPortal)
return true;
return false;
}
bool isWalking() bool isWalking()
{ {
@ -414,12 +405,10 @@ enum class PathNodeType : uint8
NODE_PREPATH = 0, NODE_PREPATH = 0,
NODE_PATH = 1, NODE_PATH = 1,
NODE_NODE = 2, NODE_NODE = 2,
NODE_PORTAL = 3, // values 3 (NODE_AREA_TRIGGER), 6 (NODE_TELEPORT), 7 (NODE_STATIC_PORTAL)
// reserved for future use — handlers not yet wired up.
NODE_TRANSPORT = 4, NODE_TRANSPORT = 4,
NODE_FLIGHTPATH = 5, NODE_FLIGHTPATH = 5
NODE_TELEPORT = 6,
NODE_FLYING_MOUNT = 7,
NODE_AREA_TRIGGER = 8
}; };
struct PathNodePoint struct PathNodePoint
@ -673,7 +662,6 @@ public:
void generateNpcNodes(); void generateNpcNodes();
void generateStartNodes(); void generateStartNodes();
void generateAreaTriggerNodes();
void generateNodes(); void generateNodes();
void generateTransportNodes(); void generateTransportNodes();
void generateZoneMeanNodes(); void generateZoneMeanNodes();