mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
refactor(Core/Travel): Remove teleportSpell + NODE_TELEPORT + PortalNode + hearthstone/mage A* injection (staticPortal kept)
This commit is contained in:
parent
6079863fce
commit
45a3189d4b
@ -3342,46 +3342,6 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
|
|||||||
return bot->ActivateTaxiPathTo(route, flightMaster, 0);
|
return bot->ActivateTaxiPathTo(route, flightMaster, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
case PathNodeType::NODE_TELEPORT:
|
|
||||||
{
|
|
||||||
if (!next.entry)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Can't cast while flying — let the bot land first.
|
|
||||||
bool const canCastNow = !bot->IsFlying();
|
|
||||||
|
|
||||||
if (next.entry == 8690) // Hearthstone
|
|
||||||
{
|
|
||||||
if (canCastNow)
|
|
||||||
{
|
|
||||||
bool const ok = botAI->DoSpecificAction("hearthstone",
|
|
||||||
Event("move action"), true);
|
|
||||||
if (ok)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (canCastNow)
|
|
||||||
{
|
|
||||||
// Mage city portal / similar spell — dismount, drop
|
|
||||||
// shapeshift, queue cast. We don't gate on reagents (no
|
|
||||||
// "has reagents for" value on AC); the server-side cast
|
|
||||||
// attempt will fail cleanly if reagents are missing.
|
|
||||||
if (bot->IsMounted())
|
|
||||||
bot->Dismount();
|
|
||||||
botAI->RemoveShapeshift();
|
|
||||||
if (botAI->DoSpecificAction(
|
|
||||||
"cast",
|
|
||||||
Event("rpg action", std::to_string(next.entry)), true))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast didn't happen or failed — clear the cached path so
|
|
||||||
// the next tick re-resolves cleanly instead of retrying the
|
|
||||||
// same teleport edge that just failed.
|
|
||||||
AI_VALUE(LastMovement&, "last movement").setPath(TravelPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -912,13 +912,6 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teleport spell (hearthstone et al.): fire on the next-step marker.
|
|
||||||
if (nextP->type == PathNodeType::NODE_TELEPORT)
|
|
||||||
{
|
|
||||||
cutTo(*nextP, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flight path: interact with flight master when in range.
|
// Flight path: interact with flight master when in range.
|
||||||
if (startP->type == PathNodeType::NODE_FLIGHTPATH &&
|
if (startP->type == PathNodeType::NODE_FLIGHTPATH &&
|
||||||
startPos.distance(startP->point) < INTERACTION_DISTANCE)
|
startPos.distance(startP->point) < INTERACTION_DISTANCE)
|
||||||
@ -1239,14 +1232,6 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
|
|||||||
// Full taxi waypoint route; same reasoning as transport.
|
// Full taxi waypoint route; same reasoning as transport.
|
||||||
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
|
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
|
||||||
}
|
}
|
||||||
else if (nodePath->getPathType() == TravelNodePathType::teleportSpell)
|
|
||||||
{
|
|
||||||
// Hearthstone or spell-cast teleport edge: emit a paired
|
|
||||||
// NODE_TELEPORT (entry = exit) so HandleSpecialMovement can
|
|
||||||
// dispatch the cast when the head reaches the entry point.
|
|
||||||
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
|
|
||||||
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<WorldPosition> path = nodePath->GetPath();
|
std::vector<WorldPosition> path = nodePath->GetPath();
|
||||||
@ -1435,8 +1420,6 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
|
|
||||||
std::vector<TravelNodeStub*> open, closed;
|
std::vector<TravelNodeStub*> open, closed;
|
||||||
|
|
||||||
std::vector<TravelNode*> portNodes; // synthetic teleport/portal edges
|
|
||||||
|
|
||||||
if (bot)
|
if (bot)
|
||||||
{
|
{
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
@ -1473,97 +1456,20 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
PAI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::travel));
|
PAI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::travel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hearthstone (item 6948 / spell 8690): inject a synthetic
|
|
||||||
// teleport edge from start to the node nearest the bot's
|
|
||||||
// home bind, so A* can pick hearthing over walking.
|
|
||||||
if (bot->IsAlive() && bot->HasItemCount(6948, 1))
|
|
||||||
{
|
|
||||||
WorldPosition homePos = AI_VALUE(WorldPosition, "home bind");
|
|
||||||
std::vector<WorldPosition> dummy;
|
|
||||||
TravelNode* homeNode = sTravelNodeMap.getNode(homePos, dummy, nullptr, 50.0f);
|
|
||||||
if (homeNode && homeNode != start)
|
|
||||||
{
|
|
||||||
PortalNode* portNode = new PortalNode(start);
|
|
||||||
portNode->SetPortal(start, homeNode, 8690);
|
|
||||||
|
|
||||||
TravelNodeStub* hsStub = &m_stubs.insert(std::make_pair(
|
|
||||||
static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second;
|
|
||||||
|
|
||||||
// Cost: max(2, (10 - deathCount) * MINUTE) — matches
|
|
||||||
// reference exactly, including the uint32 underflow at
|
|
||||||
// deathCount > 10 (which makes hearthstone prohibitive
|
|
||||||
// for very-dead bots — apparently intentional).
|
|
||||||
hsStub->costFromStart = std::max<uint32>(2,
|
|
||||||
(10 - AI_VALUE(uint32, "death count")) * MINUTE);
|
|
||||||
hsStub->heuristic = hsStub->dataNode->fDist(goal) / botSpeed;
|
|
||||||
hsStub->totalCost = hsStub->costFromStart + hsStub->heuristic;
|
|
||||||
|
|
||||||
open.push_back(hsStub);
|
|
||||||
hsStub->open = true;
|
|
||||||
portNodes.push_back(portNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mage teleport spells: 3561 Stormwind, 3562 Ironforge, 3563 Undercity,
|
|
||||||
// 3565 Darnassus, 3566 Thunder Bluff, 3567 Orgrimmar, 18960 Moonglade.
|
|
||||||
// Inject one synthetic teleport edge per known + ready spell.
|
|
||||||
static const uint32 teleSpells[] = {3561, 3562, 3563, 3565, 3566, 3567, 18960};
|
|
||||||
for (uint32 spellId : teleSpells)
|
|
||||||
{
|
|
||||||
if (!bot->IsAlive() || bot->IsInCombat())
|
|
||||||
break;
|
|
||||||
if (!bot->HasSpell(spellId))
|
|
||||||
continue;
|
|
||||||
if (bot->HasSpellCooldown(spellId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SpellTargetPosition const* stp =
|
|
||||||
sSpellMgr->GetSpellTargetPosition(spellId, EFFECT_0);
|
|
||||||
if (!stp)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition telePos(stp->target_mapId, stp->target_X,
|
|
||||||
stp->target_Y, stp->target_Z, 0.0f);
|
|
||||||
std::vector<WorldPosition> dummy;
|
|
||||||
TravelNode* destNode = sTravelNodeMap.getNode(telePos, dummy, nullptr, 10.0f);
|
|
||||||
if (!destNode || destNode == start)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PortalNode* portNode = new PortalNode(start);
|
|
||||||
portNode->SetPortal(start, destNode, spellId);
|
|
||||||
|
|
||||||
TravelNodeStub* tsStub = &m_stubs.insert(std::make_pair(
|
|
||||||
static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second;
|
|
||||||
|
|
||||||
tsStub->costFromStart = MINUTE; // cheaper than ~1-min walk
|
|
||||||
tsStub->heuristic = tsStub->dataNode->fDist(goal) / botSpeed;
|
|
||||||
tsStub->totalCost = tsStub->costFromStart + tsStub->heuristic;
|
|
||||||
|
|
||||||
open.push_back(tsStub);
|
|
||||||
tsStub->open = true;
|
|
||||||
portNodes.push_back(portNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
startStub->currentGold = bot->GetMoney();
|
startStub->currentGold = bot->GetMoney();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open.empty() && !start->hasRouteTo(goal))
|
if (!start->hasRouteTo(goal))
|
||||||
{
|
|
||||||
for (auto* p : portNodes)
|
|
||||||
delete p;
|
|
||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
|
||||||
|
|
||||||
// Min-heap: smallest f at front
|
// Min-heap: smallest f at front
|
||||||
auto heapComp = [](TravelNodeStub* i, TravelNodeStub* j) { return i->totalCost > j->totalCost; };
|
auto heapComp = [](TravelNodeStub* i, TravelNodeStub* j) { return i->totalCost > j->totalCost; };
|
||||||
|
|
||||||
open.push_back(startStub);
|
open.push_back(startStub);
|
||||||
startStub->open = true;
|
startStub->open = true;
|
||||||
// Heapify all of open in one pass — covers both startStub and any
|
std::push_heap(open.begin(), open.end(), heapComp);
|
||||||
// PortalNode stubs injected above.
|
|
||||||
std::make_heap(open.begin(), open.end(), heapComp);
|
|
||||||
|
|
||||||
while (!open.empty())
|
while (!open.empty())
|
||||||
{
|
{
|
||||||
@ -1589,12 +1495,7 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
reverse(path.begin(), path.end());
|
reverse(path.begin(), path.end());
|
||||||
|
return TravelNodeRoute(path);
|
||||||
// Successful route: hand off ownership of any synthetic
|
|
||||||
// PortalNodes injected at the head. Caller (GetFullPath)
|
|
||||||
// is expected to call cleanTempNodes() when done with the
|
|
||||||
// route — see the call site for the lifecycle.
|
|
||||||
return TravelNodeRoute(path, portNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& link : *currentNode->dataNode->getLinks()) // for each successor n' of n
|
for (auto const& link : *currentNode->dataNode->getLinks()) // for each successor n' of n
|
||||||
@ -1633,9 +1534,6 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A* exhausted open without reaching goal. Clean up synthetic nodes.
|
|
||||||
for (auto* p : portNodes)
|
|
||||||
delete p;
|
|
||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1828,7 +1726,6 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
if (transportEntry)
|
if (transportEntry)
|
||||||
{
|
{
|
||||||
path = route.BuildPath({botPos}, endProbe, bot);
|
path = route.BuildPath({botPos}, endProbe, bot);
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1852,7 +1749,6 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
if (!startPathOk)
|
if (!startPathOk)
|
||||||
{
|
{
|
||||||
badStartNodes.push_back(s);
|
badStartNodes.push_back(s);
|
||||||
route.cleanTempNodes();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1861,50 +1757,10 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
// ResolveMovePath cycles can reuse it.
|
// ResolveMovePath cycles can reuse it.
|
||||||
beginPath = pathToStart;
|
beginPath = pathToStart;
|
||||||
path = route.BuildPath(pathToStart, endProbe, bot);
|
path = route.BuildPath(pathToStart, endProbe, bot);
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No graph route found. Last-resort hearthstone fallback (reference
|
|
||||||
// also does this): if bot has hearthstone item and is alive, treat
|
|
||||||
// the bot's current position as a one-off node and try routing from
|
|
||||||
// it to each endCandidate via the hearthstone PortalNode edge.
|
|
||||||
if (Player* player = dynamic_cast<Player*>(bot))
|
|
||||||
{
|
|
||||||
if (player->IsAlive() && player->HasItemCount(6948, 1))
|
|
||||||
{
|
|
||||||
TravelNode* botNode = new TravelNode(botPos, "Bot Pos", false);
|
|
||||||
botNode->setPoint(botPos);
|
|
||||||
|
|
||||||
for (TravelNode* e : endCandidates)
|
|
||||||
{
|
|
||||||
if (!e || std::find(badEndNodes.begin(), badEndNodes.end(), e) != badEndNodes.end())
|
|
||||||
continue;
|
|
||||||
TravelNodeRoute route = GetNodeRoute(botNode, e, player);
|
|
||||||
if (route.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Build the end-side path again for this candidate.
|
|
||||||
WorldPosition endNodePos = *e->getPosition();
|
|
||||||
std::vector<WorldPosition> endProbe;
|
|
||||||
if (endNodePos.GetMapId() == destination.GetMapId())
|
|
||||||
{
|
|
||||||
Unit* pathBot = (bot && bot->GetMapId() == destination.GetMapId()) ? bot : nullptr;
|
|
||||||
endProbe = endNodePos.getPathTo(destination, pathBot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
endProbe = {endNodePos, destination};
|
|
||||||
|
|
||||||
route.addTempNodes({botNode}); // transfer ownership of botNode
|
|
||||||
path = route.BuildPath({botPos}, endProbe, bot);
|
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
delete botNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path; // empty
|
return path; // empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -95,10 +95,7 @@ enum class TravelNodePathType : uint8
|
|||||||
areaTrigger = 2,
|
areaTrigger = 2,
|
||||||
transport = 3,
|
transport = 3,
|
||||||
flightPath = 4,
|
flightPath = 4,
|
||||||
// Teleport-spell edges (hearthstone, mage portals). Generated at A*
|
// value 5 reserved (was teleportSpell — removed)
|
||||||
// search start via PortalNode injection; consumed by
|
|
||||||
// HandleSpecialMovement's NODE_TELEPORT case.
|
|
||||||
teleportSpell = 5,
|
|
||||||
staticPortal = 6
|
staticPortal = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -410,26 +407,6 @@ protected:
|
|||||||
// uint32 transportId = 0;
|
// uint32 transportId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Synthetic A* node injected at search start to represent a teleport-spell
|
|
||||||
// (hearthstone, mage portal, etc.) as an alternative travel edge. Owned
|
|
||||||
// by GetNodeRoute caller; deleted after the route is built.
|
|
||||||
class PortalNode : public TravelNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PortalNode(TravelNode* baseNode) : TravelNode(baseNode) {}
|
|
||||||
|
|
||||||
void SetPortal(TravelNode* baseNode, TravelNode* endNode, uint32 portalSpell)
|
|
||||||
{
|
|
||||||
nodeName = baseNode->getName();
|
|
||||||
point = *baseNode->getPosition();
|
|
||||||
paths.clear();
|
|
||||||
links.clear();
|
|
||||||
TravelNodePath path(0.1f, 0.1f, (uint8)TravelNodePathType::teleportSpell,
|
|
||||||
portalSpell, true);
|
|
||||||
setPathTo(endNode, path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Route step type
|
// Route step type
|
||||||
enum class PathNodeType : uint8
|
enum class PathNodeType : uint8
|
||||||
{
|
{
|
||||||
@ -439,10 +416,7 @@ enum class PathNodeType : uint8
|
|||||||
NODE_AREA_TRIGGER = 3,
|
NODE_AREA_TRIGGER = 3,
|
||||||
NODE_TRANSPORT = 4,
|
NODE_TRANSPORT = 4,
|
||||||
NODE_FLIGHTPATH = 5,
|
NODE_FLIGHTPATH = 5,
|
||||||
// Teleport-spell endpoint (hearthstone, mage portal). Emitted by
|
// value 6 reserved (was NODE_TELEPORT — removed with teleportSpell)
|
||||||
// TravelNodeRoute::BuildPath when traversing a teleportSpell-type
|
|
||||||
// edge; consumed by HandleSpecialMovement.
|
|
||||||
NODE_TELEPORT = 6,
|
|
||||||
NODE_STATIC_PORTAL = 7
|
NODE_STATIC_PORTAL = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -575,14 +549,6 @@ public:
|
|||||||
{
|
{
|
||||||
nodes = nodes1;
|
nodes = nodes1;
|
||||||
}
|
}
|
||||||
TravelNodeRoute(std::vector<TravelNode*> nodes1,
|
|
||||||
std::vector<TravelNode*> const& tempNodes_)
|
|
||||||
{
|
|
||||||
nodes = nodes1;
|
|
||||||
if (!tempNodes_.empty())
|
|
||||||
addTempNodes(tempNodes_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEmpty() { return nodes.empty(); }
|
bool isEmpty() { return nodes.empty(); }
|
||||||
|
|
||||||
bool hasNode(TravelNode* node)
|
bool hasNode(TravelNode* node)
|
||||||
@ -593,19 +559,6 @@ public:
|
|||||||
|
|
||||||
std::vector<TravelNode*> getNodes() { return nodes; }
|
std::vector<TravelNode*> getNodes() { return nodes; }
|
||||||
|
|
||||||
// Take ownership of synthetic A* nodes (PortalNode etc.). Must call
|
|
||||||
// cleanTempNodes() to delete them when the route is no longer needed.
|
|
||||||
void addTempNodes(std::vector<TravelNode*> const& tempNodes_)
|
|
||||||
{
|
|
||||||
tempNodes.insert(tempNodes.end(), tempNodes_.begin(), tempNodes_.end());
|
|
||||||
}
|
|
||||||
void cleanTempNodes()
|
|
||||||
{
|
|
||||||
for (auto* n : tempNodes)
|
|
||||||
delete n;
|
|
||||||
tempNodes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelPath BuildPath(
|
TravelPath BuildPath(
|
||||||
std::vector<WorldPosition> pathToStart = {},
|
std::vector<WorldPosition> pathToStart = {},
|
||||||
std::vector<WorldPosition> pathToEnd = {},
|
std::vector<WorldPosition> pathToEnd = {},
|
||||||
@ -619,7 +572,6 @@ private:
|
|||||||
return std::find(nodes.begin(), nodes.end(), node);
|
return std::find(nodes.begin(), nodes.end(), node);
|
||||||
}
|
}
|
||||||
std::vector<TravelNode*> nodes;
|
std::vector<TravelNode*> nodes;
|
||||||
std::vector<TravelNode*> tempNodes; // owned synthetic nodes (PortalNode etc.)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A node container to aid A* calculations with nodes.
|
// A node container to aid A* calculations with nodes.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user