Improve AdventureBot instance and flight navigation
This commit is contained in:
1 parent
be049baacc
commit
3c361e435a
14 files changed
+525
-63
No files matched your search
@@ -3,6 +3,7 @@
|
||||
#include "ClientlessTravelGraphMgr.h"
|
||||
#include "ClientlessTransportMgr.h"
|
||||
#include "ClientlessFlightMgr.h"
|
||||
#include "ClientlessLuaEngine.h"
|
||||
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
@@ -125,7 +126,7 @@ ClientlessPortalUse const* ClientlessTravelGraphMgr::GetPortalUse(uint32 edgeDat
|
||||
return itr != _portalUses.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
bool ClientlessTravelGraphMgr::PortalPrerequisites(Player const* player,uint32 edgeDatabaseId) const
|
||||
bool ClientlessTravelGraphMgr::PortalNativePrerequisites(Player const* player,uint32 edgeDatabaseId) const
|
||||
{
|
||||
auto portal=GetPortalUse(edgeDatabaseId);
|
||||
auto info=portal?sObjectMgr->GetGameObjectTemplate(portal->Entry):nullptr;
|
||||
@@ -136,6 +137,23 @@ bool ClientlessTravelGraphMgr::PortalPrerequisites(Player const* player,uint32 e
|
||||
return !portal->Requirements.empty();
|
||||
}
|
||||
|
||||
bool ClientlessTravelGraphMgr::AdventurePortalUnlockAllowed(Player const* player,
|
||||
uint32 edgeDatabaseId) const
|
||||
{
|
||||
// AdventureBots cannot acquire quest progress. Once they have reached the
|
||||
// Cataclysm level range, graph-bound public travel portals are therefore
|
||||
// considered unlocked. This does not bypass faction/area/node policy or
|
||||
// make arbitrary, unbound GameObjects usable.
|
||||
return player && player->getLevel() >= 80 && GetPortalUse(edgeDatabaseId) &&
|
||||
sClientlessLuaEngine->GetAdventureProfile(player->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
bool ClientlessTravelGraphMgr::PortalPrerequisites(Player const* player,uint32 edgeDatabaseId) const
|
||||
{
|
||||
return PortalNativePrerequisites(player,edgeDatabaseId) ||
|
||||
AdventurePortalUnlockAllowed(player,edgeDatabaseId);
|
||||
}
|
||||
|
||||
bool ClientlessTravelGraphMgr::Reload()
|
||||
{
|
||||
_positionProbes.Clear();
|
||||
@@ -623,6 +641,10 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
}
|
||||
bool const flightEnabled = sClientlessFlightMgr->Enabled(player);
|
||||
constexpr std::uint64_t MaxFlightEstimatesPerPlan = 16;
|
||||
// Preserve part of the global budget for nodes reached later in the
|
||||
// search. In particular, a flight to a portal source must still leave
|
||||
// enough probes for the flight from its destination map to the goal.
|
||||
constexpr std::uint64_t MaxFlightEstimatesPerOrigin = 6;
|
||||
auto flightCost = [&](ClientlessTravelNode const& from, uint32 map, float x, float y, float z, bool access = false)
|
||||
{
|
||||
if (!flightEnabled || from.MapId != map ||
|
||||
@@ -815,9 +837,15 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
ClientlessTravelNode departure;
|
||||
departure.Key="flight_departure";
|
||||
departure.MapId=player->GetMapId();departure.X=player->GetPositionX();departure.Y=player->GetPositionY();departure.Z=player->GetPositionZ();
|
||||
float air=blockedBridgeKeys && blockedBridgeKeys->count("flight:flight_departure->"+node.Key)?
|
||||
std::numeric_limits<float>::infinity():flightCost(departure,node.MapId,node.X,node.Y,node.Z,!reachable);
|
||||
if(std::isfinite(air) && (!reachable || air<entryNav.PathLength))
|
||||
// A valid local MMAP entry is the normal, organic graph access and
|
||||
// must not spend the bounded flight-estimate budget. Free flight is
|
||||
// the recovery/re-entry mode for nodes that local navigation cannot
|
||||
// reach, not a shortcut racing every nearby pavement node.
|
||||
float air = std::numeric_limits<float>::infinity();
|
||||
if (!reachable && !(blockedBridgeKeys &&
|
||||
blockedBridgeKeys->count("flight:flight_departure->"+node.Key)))
|
||||
air=flightCost(departure,node.MapId,node.X,node.Y,node.Z,true);
|
||||
if(std::isfinite(air) && !reachable)
|
||||
{flightRoots.insert(node.Key);startCost=air;}
|
||||
else
|
||||
{
|
||||
@@ -1220,29 +1248,37 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
float bestGoalCost = std::numeric_limits<float>::infinity();
|
||||
std::string bestGoalState;
|
||||
bool bestFinalFlight = false;
|
||||
// Free flight is a macro connector, not a reason to turn every graph map
|
||||
// into a complete directed graph. A flyer only needs direct links to the
|
||||
// requested destination and to nodes that enter a map-changing or physical
|
||||
// transport edge (portal, ship, zeppelin or tram). Taxi nodes are excluded:
|
||||
// a free flyer can already reach their same-map destination directly, and
|
||||
// adding every taxi source recreates an almost-complete graph. Ordinary
|
||||
// same-map ground
|
||||
// nodes remain reachable through their recorded graph, while a location
|
||||
// goal is evaluated directly from every expanded node below.
|
||||
std::unordered_set<std::string> flightConnectorNodes;
|
||||
flightConnectorNodes.insert(destinationNode);
|
||||
for (auto const& edgeGroup : _edges)
|
||||
for (ClientlessTravelEdge const& edge : edgeGroup.second)
|
||||
// Every permitted node is a potential way back into the persistent graph.
|
||||
// Do not materialize an all-to-all air graph, though: candidates are ranked
|
||||
// on demand below and the shared per-plan Estimate cap remains authoritative.
|
||||
// This is important for isolated anchors such as the capital Cataclysm
|
||||
// portals, but is deliberately not portal-specific.
|
||||
auto flightRemainderLowerBound = [&](ClientlessTravelNode const& candidate)
|
||||
{
|
||||
ClientlessTravelNode const& destination = destinationItr->second;
|
||||
auto lowerBoundToGoal = [&](ClientlessTravelNode const& node)
|
||||
{
|
||||
auto fromItr = _nodes.find(edge.FromNode);
|
||||
auto toItr = _nodes.find(edge.ToNode);
|
||||
bool const changesMap = fromItr != _nodes.end() && toItr != _nodes.end() &&
|
||||
fromItr->second.MapId != toItr->second.MapId;
|
||||
bool const physicalTransport = edge.Type == "portal" || edge.Type == "ship" ||
|
||||
edge.Type == "zeppelin" || edge.Type == "tram" || edge.Type == "transport";
|
||||
if (changesMap || physicalTransport)
|
||||
flightConnectorNodes.insert(edge.FromNode);
|
||||
}
|
||||
if (node.MapId != destination.MapId)
|
||||
return std::numeric_limits<float>::infinity();
|
||||
float dx = destination.X-node.X, dy = destination.Y-node.Y, dz = destination.Z-node.Z;
|
||||
return 40.0f + std::sqrt(dx*dx+dy*dy+dz*dz)*0.4f;
|
||||
};
|
||||
float best = lowerBoundToGoal(candidate);
|
||||
auto edges = _edges.find(candidate.Key);
|
||||
if (edges != _edges.end())
|
||||
for (ClientlessTravelEdge const& edge : edges->second)
|
||||
{
|
||||
if ((!RescueEdgeAllowed(edge) || !sAdventureAreaMgr->AllowsEdge(player,edge)))
|
||||
continue;
|
||||
if (blockedEdgeIds && edge.DatabaseId && blockedEdgeIds->count(edge.DatabaseId))
|
||||
continue;
|
||||
auto next = _nodes.find(edge.ToNode);
|
||||
if (next == _nodes.end())
|
||||
continue;
|
||||
best = std::min(best, edge.Cost + lowerBoundToGoal(next->second));
|
||||
}
|
||||
return best;
|
||||
};
|
||||
// C8.20: a virtual entry is represented in the queue by its FAR
|
||||
// endpoint. Check exits BEFORE that endpoint independently: otherwise
|
||||
// an exit halfway along the road would pay for the entire remaining edge.
|
||||
@@ -1379,10 +1415,11 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
// ground transit. Once reached during a planned route, its only legal
|
||||
// continuation is the portal edge itself. Physical AreaTriggers may
|
||||
// fire even before the route executor advances to that edge.
|
||||
bool const isPortalSource =
|
||||
current.Value.Node.rfind("portal_at_", 0) == 0 &&
|
||||
current.Value.Node.size() >= 7 &&
|
||||
current.Value.Node.compare(current.Value.Node.size() - 7, 7, "_source") == 0;
|
||||
auto const sourceEdges = _edges.find(current.Value.Node);
|
||||
bool const isPortalSource = sourceEdges != _edges.end() &&
|
||||
std::any_of(sourceEdges->second.begin(), sourceEdges->second.end(),
|
||||
[](ClientlessTravelEdge const& edge)
|
||||
{ return edge.Type == "portal" || edge.Type == "portal_use"; });
|
||||
bool const portalArrival = isPortalSource && previous.find(currentKey) != previous.end();
|
||||
|
||||
// Persistent TravelGraph edges always reset the bridge guard.
|
||||
@@ -1393,7 +1430,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
for (ClientlessTravelEdge const& edge : edgeItr->second)
|
||||
{
|
||||
if ((!RescueEdgeAllowed(edge) || !sAdventureAreaMgr->AllowsEdge(player,edge))) continue;
|
||||
if (portalArrival && edge.Type != "portal")
|
||||
if (portalArrival && edge.Type != "portal" && edge.Type != "portal_use")
|
||||
continue;
|
||||
if (blockedEdgeIds && edge.DatabaseId && blockedEdgeIds->count(edge.DatabaseId))
|
||||
continue;
|
||||
@@ -1421,15 +1458,15 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
// Dynamic flights connect real nodes; never the synthetic entry waypoints.
|
||||
// Include them before the local-bridge guard: a flight is a separate
|
||||
// macro travel mode with its own native departure and landing checks.
|
||||
if (flightEnabled && !portalArrival)
|
||||
if (flightEnabled && !portalArrival && trace.FlightCalls < MaxFlightEstimatesPerPlan)
|
||||
{
|
||||
auto const& origin=_nodes.at(current.Value.Node);
|
||||
for (std::string const& targetKey : flightConnectorNodes)
|
||||
std::vector<std::pair<float, std::string>> macroFlightCandidates;
|
||||
std::vector<std::pair<float, std::string>> flightCandidates;
|
||||
flightCandidates.reserve(_nodes.size());
|
||||
for (auto const& candidate : _nodes)
|
||||
{
|
||||
auto targetItr = _nodes.find(targetKey);
|
||||
if (targetItr == _nodes.end())
|
||||
continue;
|
||||
auto const& target=targetItr->second;
|
||||
auto const& target=candidate.second;
|
||||
if (target.Key==origin.Key || target.MapId!=origin.MapId) continue;
|
||||
State next{target.Key,false};std::string key=stateKey(next);
|
||||
auto old=distance.find(key);
|
||||
@@ -1441,6 +1478,58 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
if(old!=distance.end() && old->second<=current.Cost+lowerBound)continue;
|
||||
if ((!RescueNodeAllowed(target) || !sAdventureAreaMgr->AllowsNode(player,target))) continue;
|
||||
if (blockedBridgeKeys && blockedBridgeKeys->count("flight:"+origin.Key+"->"+target.Key)) continue;
|
||||
float const remainder = flightRemainderLowerBound(target);
|
||||
if (!std::isfinite(remainder))
|
||||
continue;
|
||||
bool macroConnector = target.Key == destinationNode;
|
||||
auto outgoing = _edges.find(target.Key);
|
||||
if (outgoing != _edges.end())
|
||||
for (ClientlessTravelEdge const& edge : outgoing->second)
|
||||
{
|
||||
auto next = _nodes.find(edge.ToNode);
|
||||
bool const changesMap = next != _nodes.end() &&
|
||||
next->second.MapId != target.MapId;
|
||||
bool const physical = edge.Type == "portal" ||
|
||||
edge.Type == "portal_use" || edge.Type == "ship" ||
|
||||
edge.Type == "zeppelin" || edge.Type == "tram" ||
|
||||
edge.Type == "transport";
|
||||
if (changesMap || physical)
|
||||
{
|
||||
macroConnector = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto& candidates = macroConnector ? macroFlightCandidates : flightCandidates;
|
||||
candidates.emplace_back(lowerBound + remainder, target.Key);
|
||||
}
|
||||
std::sort(macroFlightCandidates.begin(), macroFlightCandidates.end(),
|
||||
[&](auto const& left, auto const& right)
|
||||
{
|
||||
// Every newly reached portal/transport endpoint must get
|
||||
// one chance to finish the requested journey before the
|
||||
// bounded probe window is spent on further connectors.
|
||||
bool const leftIsDestination = left.second == destinationNode;
|
||||
bool const rightIsDestination = right.second == destinationNode;
|
||||
if (leftIsDestination != rightIsDestination)
|
||||
return leftIsDestination;
|
||||
return left < right;
|
||||
});
|
||||
std::sort(flightCandidates.begin(), flightCandidates.end());
|
||||
macroFlightCandidates.insert(macroFlightCandidates.end(),
|
||||
flightCandidates.begin(), flightCandidates.end());
|
||||
std::uint64_t const callsAtOrigin = trace.FlightCalls;
|
||||
for (auto const& ranked : macroFlightCandidates)
|
||||
{
|
||||
if (trace.FlightCalls >= MaxFlightEstimatesPerPlan ||
|
||||
trace.FlightCalls - callsAtOrigin >= MaxFlightEstimatesPerOrigin)
|
||||
break;
|
||||
auto const& target = _nodes.at(ranked.second);
|
||||
State next{target.Key,false};std::string key=stateKey(next);
|
||||
auto old=distance.find(key);
|
||||
auto const blockedFlight = blockedBridgeKeys &&
|
||||
blockedBridgeKeys->count("flight:"+origin.Key+"->"+target.Key);
|
||||
if (blockedFlight)
|
||||
continue;
|
||||
float cost=flightCost(origin,target.MapId,target.X,target.Y,target.Z);
|
||||
if (!std::isfinite(cost)) continue;
|
||||
float score=current.Cost+cost;
|
||||
@@ -1464,10 +1553,11 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
// road at an MMAP-reachable waypoint. Only remaining portions of
|
||||
// stored, directed ground edges are admitted; no off-graph shortcut.
|
||||
// A portal source may not be treated as an ordinary intermediate node.
|
||||
bool const futurePortalSource =
|
||||
from.Key.rfind("portal_at_", 0) == 0 &&
|
||||
from.Key.size() >= 7 &&
|
||||
from.Key.compare(from.Key.size() - 7, 7, "_source") == 0;
|
||||
auto const futureEdges = _edges.find(from.Key);
|
||||
bool const futurePortalSource = futureEdges != _edges.end() &&
|
||||
std::any_of(futureEdges->second.begin(), futureEdges->second.end(),
|
||||
[](ClientlessTravelEdge const& edge)
|
||||
{ return edge.Type == "portal" || edge.Type == "portal_use"; });
|
||||
if (!futurePortalSource && !hasPersistentContinuation && persistentHops[currentKey] > 0 &&
|
||||
!(blockedBridgeKeys &&
|
||||
blockedBridgeKeys->count(MakeLocalBridgeKey(from.Key, "virtual_ground_entry"))))
|
||||
|
||||
Reference in new issue
Block a user