mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
feat(Core/Travel): Exclude area-trigger, static-portal, teleport-spell path types from PR
This commit is contained in:
parent
905f550ca1
commit
c0e41e6ce1
@ -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",
|
||||||
|
|||||||
@ -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)
|
||||||
else if (getPathType() == TravelNodePathType::flightPath || getPathType() == TravelNodePathType::flyingMount)
|
|
||||||
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");
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user