feat(Core/Travel): Re-enable area-trigger, static-portal, and teleport-spell nodes

This commit is contained in:
bash 2026-05-30 19:34:04 +02:00
parent e7870afbd9
commit f328f455ca
3 changed files with 232 additions and 10 deletions

View File

@ -3508,6 +3508,141 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
return true;
}
case PathNodeType::NODE_AREA_TRIGGER:
{
// Pair: trigger (pointIdx) + dest (pointIdx+1).
// Bot walks into the area trigger volume; server teleports
// on entry. Bot may need quest/key prereqs to actually cross.
if (state.stepIdx + 1 >= state.steps.size())
{
state.Reset();
return false;
}
const PathNodePoint& trigger = state.steps[state.stepIdx];
const PathNodePoint& dst = state.steps[state.stepIdx + 1];
// Already on destination map — trigger fired, advance.
if (bot->GetMapId() == dst.point.GetMapId())
{
state.stepIdx += 2;
return true;
}
// Walk to the trigger position; collision with the trigger
// volume 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 quest/key.
// Abort; the do-quest yield-to-grind multiplier or next
// POI pick can reroute.
state.Reset();
return false;
}
case PathNodeType::NODE_STATIC_PORTAL:
{
// Pair: portal-GO position (pointIdx) + dest (pointIdx+1).
// Bot walks within interact range of the portal GameObject
// and sends CMSG_GAMEOBJ_USE to trigger its teleport spell.
if (state.stepIdx + 1 >= state.steps.size())
{
state.Reset();
return false;
}
const PathNodePoint& portal = state.steps[state.stepIdx];
const PathNodePoint& dst = state.steps[state.stepIdx + 1];
if (bot->GetMapId() == dst.point.GetMapId())
{
state.stepIdx += 2;
return true;
}
// Walk to portal GO position
float dist = bot->GetExactDist(portal.point.GetPositionX(),
portal.point.GetPositionY(),
portal.point.GetPositionZ());
if (dist > INTERACTION_DISTANCE)
return MoveTo(portal.point.GetMapId(),
portal.point.GetPositionX(),
portal.point.GetPositionY(),
portal.point.GetPositionZ());
// In range — find the portal GameObject and interact
if (!portal.entry)
{
state.Reset();
return false;
}
if (bot->IsMounted())
bot->Dismount();
botAI->RemoveShapeshift();
GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects");
for (ObjectGuid const& guid : nearGOs)
{
GameObject* go = botAI->GetGameObject(guid);
if (!go || go->GetEntry() != portal.entry)
continue;
if (!bot->GetGameObjectIfCanInteractWith(guid, MAX_GAMEOBJECT_TYPE))
continue;
WorldPacket packet(CMSG_GAMEOBJ_USE);
packet << guid;
bot->GetSession()->QueuePacket(new WorldPacket(packet));
return true;
}
// GO not found nearby — abort and let next tick try again
state.Reset();
return false;
}
case PathNodeType::NODE_TELEPORT:
{
// Teleport-spell node: hearthstone (spell 8690) or class
// teleport spells (mage/druid). `entry` holds the spell ID.
uint32 spellId = pt.entry;
if (!spellId)
{
state.Reset();
return false;
}
if (bot->IsInFlight() || bot->IsNonMeleeSpellCast(false))
return true; // wait
if (bot->IsMounted())
bot->Dismount();
botAI->RemoveShapeshift();
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.
state.stepIdx++;
return true;
}
case PathNodeType::NODE_TRANSPORT:
{
if (state.stepIdx + 1 >= state.steps.size())

View File

@ -875,7 +875,17 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
continue;
}
if (nodePath->getPathType() == TravelNodePathType::transport)
if (nodePath->getPathType() == TravelNodePathType::areaTrigger)
{
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_AREA_TRIGGER, nodePath->getPathObject());
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_AREA_TRIGGER, nodePath->getPathObject());
}
else if (nodePath->getPathType() == TravelNodePathType::staticPortal)
{
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_STATIC_PORTAL, nodePath->getPathObject());
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_STATIC_PORTAL, nodePath->getPathObject());
}
else if (nodePath->getPathType() == TravelNodePathType::transport)
{
// Emit the transport's full waypoint route, not just board+exit.
// Intermediate points carry NODE_TRANSPORT type so the executor
@ -888,6 +898,11 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
// Full taxi waypoint route; same reasoning as transport.
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
}
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
{
std::vector<WorldPosition> path = nodePath->GetPath();
@ -896,6 +911,11 @@ 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.
path.pop_back();
if (path.size() > 1 && prevNode->isPortal() &&
nodePath->getPathType() != TravelNodePathType::areaTrigger &&
nodePath->getPathType() != TravelNodePathType::staticPortal)
path.erase(path.begin());
if (path.size() > 1 && prevNode->isTransport() &&
nodePath->getPathType() != TravelNodePathType::transport)
path.erase(path.begin());
@ -1558,6 +1578,63 @@ 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 + area-trigger link
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* outNode = TravelNodeMap::instance().addNode(outPos, nodeName, true, true);
TravelNode* inNode = TravelNodeMap::instance().getNode(inPos, nullptr, 5.0f);
if (outNode && inNode)
{
TravelNodePath travelPath(0.1f, 3.0f, (uint8)TravelNodePathType::areaTrigger, itr.first, true);
travelPath.setPath({*inNode->getPosition(), *outNode->getPosition()});
inNode->setPathTo(outNode, travelPath);
}
}
}
void TravelNodeMap::generateTransportNodes()
{
for (auto const& itr : *sObjectMgr->GetGameObjectTemplates())
@ -1661,6 +1738,8 @@ void TravelNodeMap::generateNodes()
generateStartNodes();
LOG_INFO("playerbots", "-Generating npc nodes");
generateNpcNodes();
LOG_INFO("playerbots", "-Generating area trigger nodes");
generateAreaTriggerNodes();
LOG_INFO("playerbots", "-Generating transport nodes");
generateTransportNodes();
LOG_INFO("playerbots", "-Generating zone mean nodes");

View File

@ -90,10 +90,11 @@ enum class TravelNodePathType : uint8
{
none = 0,
walk = 1,
// values 2 (areaTrigger), 5 (teleportSpell), 6 (staticPortal)
// reserved for future use — generation/execution not yet wired up.
areaTrigger = 2,
transport = 3,
flightPath = 4
flightPath = 4,
teleportSpell = 5,
staticPortal = 6
};
// A connection between two nodes.
@ -261,9 +262,14 @@ public:
return false;
}
// Portal-style link types (areaTrigger, staticPortal) are not
// generated in this PR. Stub retained so consumers compile.
bool isPortal() { return false; }
bool isPortal()
{
for (auto const& link : *getLinks())
if (link.second->getPathType() == TravelNodePathType::areaTrigger ||
link.second->getPathType() == TravelNodePathType::staticPortal)
return true;
return false;
}
bool isWalking()
{
@ -405,10 +411,11 @@ enum class PathNodeType : uint8
NODE_PREPATH = 0,
NODE_PATH = 1,
NODE_NODE = 2,
// values 3 (NODE_AREA_TRIGGER), 6 (NODE_TELEPORT), 7 (NODE_STATIC_PORTAL)
// reserved for future use — handlers not yet wired up.
NODE_AREA_TRIGGER = 3,
NODE_TRANSPORT = 4,
NODE_FLIGHTPATH = 5
NODE_FLIGHTPATH = 5,
NODE_TELEPORT = 6,
NODE_STATIC_PORTAL = 7
};
struct PathNodePoint
@ -662,6 +669,7 @@ public:
void generateNpcNodes();
void generateStartNodes();
void generateAreaTriggerNodes();
void generateNodes();
void generateTransportNodes();
void generateZoneMeanNodes();