added visible gryphon when bots using taxi-gryphon
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-10-14 21:23:04 +00:00
1 parent 24fb292cee
commit 93c3df1c95
5 files changed
+339 -2

No files matched your search

@@ -812,7 +812,39 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
sClientlessTravelMgr->CancelTravel(characterGuid);
record.EdgePath.clear();
if (!player->ActivateTaxiPathTo(taxiPathId))
// AdventureBots are performative world actors, not simulated
// participants in the player economy. Give the normal Trinity taxi
// code enough temporary money to pass its fare check, then restore the
// bot's original balance immediately after the flight has been started.
//
// Use a generous temporary balance rather than taxiCost directly:
// ActivateTaxiPathTo() may internally calculate a different total when
// a path contains multiple legs.
uint64 const originalMoney = player->GetMoney();
constexpr uint64 TemporaryTaxiMoney = 1000000000ULL;
if (originalMoney < TemporaryTaxiMoney)
player->SetMoney(TemporaryTaxiMoney);
// Clientless taxi movement runs server-side, but observers may
// otherwise see the bot flying without a visible taxi mount. Resolve
// the proper faction/node-specific taxi mount and apply it explicitly
// before starting the normal Trinity taxi flight.
uint32 const taxiMountDisplayId =
sObjectMgr->GetTaxiMountDisplayId(
sourceTaxiNodeId,
player->GetTeam(),
true);
if (taxiMountDisplayId && !player->IsMounted())
player->Mount(taxiMountDisplayId);
bool const taxiStarted = player->ActivateTaxiPathTo(taxiPathId);
if (player->GetMoney() != originalMoney)
player->SetMoney(originalMoney);
if (!taxiStarted)
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.TargetName = "node:" + step.NodeKey;
@@ -2,10 +2,14 @@
#include "Log.h"
#include "MotionMaster.h"
#include "PathGenerator.h"
#include "Player.h"
#include "World.h"
#include "WorldSession.h"
#include <algorithm>
#include <cmath>
namespace
{
constexpr float ARRIVAL_DISTANCE = 0.5f;
@@ -35,6 +39,98 @@ char const* ClientlessTravelMgr::StateName(ClientlessTravelState state)
return "Unknown";
}
bool ClientlessTravelMgr::AnalyzePath(
Player* player,
Position const& target,
ClientlessPathDiagnostics& diagnostics) const
{
diagnostics = {};
if (!player || !player->IsInWorld())
{
diagnostics.Reason = "player-not-in-world";
return false;
}
float const sx = player->GetPositionX();
float const sy = player->GetPositionY();
float const sz = player->GetPositionZ();
float const tx = target.GetPositionX();
float const ty = target.GetPositionY();
float const tz = target.GetPositionZ();
float const ddx = tx - sx;
float const ddy = ty - sy;
float const ddz = tz - sz;
diagnostics.DirectDistance = std::sqrt(ddx * ddx + ddy * ddy + ddz * ddz);
PathGenerator path(player);
diagnostics.Calculated = path.CalculatePath(tx, ty, tz);
diagnostics.PathType = uint32(path.GetPathType());
auto const& points = path.GetPath();
diagnostics.PointCount = uint32(points.size());
if (!points.empty())
{
for (std::size_t i = 1; i < points.size(); ++i)
{
float const dx = points[i].x - points[i - 1].x;
float const dy = points[i].y - points[i - 1].y;
float const dz = points[i].z - points[i - 1].z;
float const segment = std::sqrt(dx * dx + dy * dy + dz * dz);
diagnostics.PathLength += segment;
diagnostics.MaxSegmentLength = std::max(diagnostics.MaxSegmentLength, segment);
diagnostics.MaxZDelta = std::max(diagnostics.MaxZDelta, std::fabs(dz));
}
auto const& last = points.back();
float const dx = last.x - tx;
float const dy = last.y - ty;
float const dz = last.z - tz;
diagnostics.FinalDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
}
else
diagnostics.FinalDistance = diagnostics.DirectDistance;
uint32 const pathType = diagnostics.PathType;
diagnostics.Complete =
diagnostics.Calculated &&
(pathType & uint32(PATHFIND_NOPATH)) == 0 &&
(pathType & uint32(PATHFIND_INCOMPLETE)) == 0;
auto addReason = [&](char const* reason)
{
if (!diagnostics.Reason.empty())
diagnostics.Reason += ",";
diagnostics.Reason += reason;
diagnostics.Suspicious = true;
};
if (!diagnostics.Calculated)
addReason("calculate-failed");
if (pathType & uint32(PATHFIND_NOPATH))
addReason("no-path");
if (pathType & uint32(PATHFIND_INCOMPLETE))
addReason("incomplete");
if (diagnostics.PointCount == 0 && diagnostics.DirectDistance > ARRIVAL_DISTANCE)
addReason("empty-path");
if (diagnostics.MaxSegmentLength > 25.0f)
addReason("large-segment");
if (diagnostics.MaxZDelta > 8.0f)
addReason("large-z-delta");
if (diagnostics.FinalDistance > 3.0f)
addReason("far-from-target");
if (diagnostics.DirectDistance > 5.0f && diagnostics.PathLength > diagnostics.DirectDistance * 8.0f)
addReason("extreme-detour");
if (diagnostics.Reason.empty())
diagnostics.Reason = "ok";
return diagnostics.Calculated;
}
void ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record)
{
if (!player)
@@ -46,6 +142,49 @@ void ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record)
player->GetMotionMaster()->Clear();
Position const& target = record.Status.Target;
AnalyzePath(player, target, record.Status.Path);
ClientlessPathDiagnostics const& nav = record.Status.Path;
if (nav.Suspicious)
{
TC_LOG_WARN("server.adventurebot",
"[AdventureBot][Nav] path-suspicious guid=%u name='%s' map=%u "
"start=(%.3f %.3f %.3f) target=(%.3f %.3f %.3f) "
"type=0x%X calculated=%u complete=%u points=%u direct=%.2f path=%.2f "
"maxSegment=%.2f maxZDelta=%.2f finalDistance=%.2f repath=%u reason='%s'",
player->GetGUID().GetCounter(), player->GetName().c_str(), player->GetMapId(),
player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(),
target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(),
nav.PathType, nav.Calculated ? 1u : 0u, nav.Complete ? 1u : 0u,
nav.PointCount, nav.DirectDistance, nav.PathLength,
nav.MaxSegmentLength, nav.MaxZDelta, nav.FinalDistance,
record.Status.RepathCount, nav.Reason.c_str());
}
else
{
TC_LOG_INFO("server.adventurebot",
"[AdventureBot][Nav] path guid=%u name='%s' map=%u "
"type=0x%X points=%u direct=%.2f path=%.2f maxSegment=%.2f "
"maxZDelta=%.2f finalDistance=%.2f repath=%u",
player->GetGUID().GetCounter(), player->GetName().c_str(), player->GetMapId(),
nav.PathType, nav.PointCount, nav.DirectDistance, nav.PathLength,
nav.MaxSegmentLength, nav.MaxZDelta, nav.FinalDistance,
record.Status.RepathCount);
}
PathGenerator debugPath(player);
if (debugPath.CalculatePath(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()))
{
auto const& debugPoints = debugPath.GetPath();
for (std::size_t i = 0; i < debugPoints.size(); ++i)
TC_LOG_DEBUG("server.adventurebot",
"[AdventureBot][Nav] path-point guid=%u repath=%u index=%u/%u xyz=(%.3f %.3f %.3f)",
player->GetGUID().GetCounter(), record.Status.RepathCount,
uint32(i + 1), uint32(debugPoints.size()),
debugPoints[i].x, debugPoints[i].y, debugPoints[i].z);
}
player->GetMotionMaster()->MovePoint(
1,
target.GetPositionX(),
@@ -201,6 +340,12 @@ void ClientlessTravelMgr::Update(uint32 diff)
TC_LOG_WARN("server.worldserver",
"ClientlessTravel character GUID %u is stuck (remaining=%.2f, repaths=%u).",
characterGuid, distance, record.Status.RepathCount);
TC_LOG_WARN("server.adventurebot",
"[AdventureBot][Nav] stuck guid=%u name='%s' map=%u remaining=%.2f "
"repaths=%u lastPathType=0x%X lastPathPoints=%u lastReason='%s'",
characterGuid, player->GetName().c_str(), player->GetMapId(), distance,
record.Status.RepathCount, record.Status.Path.PathType,
record.Status.Path.PointCount, record.Status.Path.Reason.c_str());
continue;
}
@@ -210,6 +355,12 @@ void ClientlessTravelMgr::Update(uint32 diff)
"ClientlessTravel repathing character GUID %u (remaining=%.2f, repath=%u/%u).",
characterGuid, distance, record.Status.RepathCount, MAX_REPATHS);
TC_LOG_WARN("server.adventurebot",
"[AdventureBot][Nav] repath guid=%u name='%s' map=%u remaining=%.2f "
"noProgressMs=%u repath=%u/%u reason='no-progress'",
characterGuid, player->GetName().c_str(), player->GetMapId(), distance,
record.Status.NoProgressMs, record.Status.RepathCount, MAX_REPATHS);
LaunchPath(player, record);
}
}
@@ -7,8 +7,24 @@
#include "Common.h"
#include "Position.h"
#include <string>
#include <unordered_map>
struct TC_GAME_API ClientlessPathDiagnostics
{
bool Calculated = false;
bool Complete = false;
bool Suspicious = false;
uint32 PathType = 0;
uint32 PointCount = 0;
float DirectDistance = 0.0f;
float PathLength = 0.0f;
float MaxSegmentLength = 0.0f;
float MaxZDelta = 0.0f;
float FinalDistance = 0.0f;
std::string Reason;
};
class Player;
enum class ClientlessTravelState : uint8
@@ -28,6 +44,7 @@ struct TC_GAME_API ClientlessTravelStatus
float RemainingDistance = 0.0f;
uint32 RepathCount = 0;
uint32 NoProgressMs = 0;
ClientlessPathDiagnostics Path;
};
class TC_GAME_API ClientlessTravelMgr
@@ -36,6 +53,7 @@ public:
static ClientlessTravelMgr* instance();
bool StartTravel(Player* player, Position const& target);
bool AnalyzePath(Player* player, Position const& target, ClientlessPathDiagnostics& diagnostics) const;
void CancelTravel(uint32 characterGuid);
ClientlessTravelStatus const* GetStatus(uint32 characterGuid) const;
void Update(uint32 diff);