added visible gryphon when bots using taxi-gryphon
This commit is contained in:
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);
|
||||
|
||||
@@ -10,7 +10,7 @@ intend("socialize", { city = "Ironforge", venue = "tavern", activity = "drink",
|
||||
-- approach_distance = 2
|
||||
--})
|
||||
queue_action("travel_to_location", {
|
||||
id = "goldshire_tavern"
|
||||
id = "kharanos_tavern"
|
||||
})
|
||||
-- C8.6 incremental travel-graph example.
|
||||
-- Enable after the corresponding graph nodes/edges exist in cata_world.
|
||||
|
||||
@@ -1446,6 +1446,139 @@ static bool HandleClientlessTravelReload(ChatHandler* handler, char const* /*arg
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PrintClientlessPathDiagnostics(
|
||||
ChatHandler* handler,
|
||||
uint32 characterGuid,
|
||||
ClientlessPathDiagnostics const& nav)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"Nav GUID %u: calculated=%s complete=%s suspicious=%s type=0x%X reason='%s'",
|
||||
characterGuid,
|
||||
nav.Calculated ? "yes" : "no",
|
||||
nav.Complete ? "yes" : "no",
|
||||
nav.Suspicious ? "yes" : "no",
|
||||
nav.PathType,
|
||||
nav.Reason.c_str());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
" points=%u direct=%.2f path=%.2f maxSegment=%.2f maxZDelta=%.2f finalDistance=%.2f",
|
||||
nav.PointCount, nav.DirectDistance, nav.PathLength,
|
||||
nav.MaxSegmentLength, nav.MaxZDelta, nav.FinalDistance);
|
||||
}
|
||||
|
||||
static bool HandleClientlessNavInfo(ChatHandler* handler, char const* args)
|
||||
{
|
||||
uint32 characterGuid = 0;
|
||||
if (!ParseCharacterGuid(handler, args, characterGuid))
|
||||
{
|
||||
handler->SendSysMessage("Usage: .clientless navinfo <characterGuid>");
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientlessTravelStatus const* status = sClientlessTravelMgr->GetStatus(characterGuid);
|
||||
if (!status)
|
||||
{
|
||||
handler->PSendSysMessage("Clientless GUID %u has no travel/navigation state.", characterGuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
PrintClientlessPathDiagnostics(handler, characterGuid, status->Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleClientlessNavTest(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!handler || !handler->GetSession())
|
||||
return true;
|
||||
|
||||
std::istringstream input(args ? args : "");
|
||||
std::string first;
|
||||
input >> first;
|
||||
|
||||
bool const locationMode = (first == "location");
|
||||
uint32 characterGuid = 0;
|
||||
std::string locationKey;
|
||||
|
||||
if (locationMode)
|
||||
{
|
||||
input >> characterGuid >> locationKey;
|
||||
if (!characterGuid || locationKey.empty())
|
||||
{
|
||||
handler->SendSysMessage("Usage: .clientless navtest location <characterGuid> <locationKey>");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char* end = nullptr;
|
||||
unsigned long const parsed = std::strtoul(first.c_str(), &end, 10);
|
||||
if (first.empty() || !end || *end != '\0' || parsed == 0 || parsed > 0xFFFFFFFFul)
|
||||
{
|
||||
handler->SendSysMessage("Usage: .clientless navtest <characterGuid> OR .clientless navtest location <characterGuid> <locationKey>");
|
||||
return false;
|
||||
}
|
||||
characterGuid = uint32(parsed);
|
||||
}
|
||||
|
||||
WorldSession* session = sWorld->FindClientlessSession(characterGuid);
|
||||
Player* bot = session ? session->GetPlayer() : nullptr;
|
||||
if (!bot || !bot->IsInWorld())
|
||||
{
|
||||
handler->PSendSysMessage("Clientless GUID %u is not online in world.", characterGuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
Position target;
|
||||
if (locationMode)
|
||||
{
|
||||
QueryResult result = WorldDatabase.PQuery(
|
||||
"SELECT map,position_x,position_y,position_z,orientation "
|
||||
"FROM vcore_clientless_world_point WHERE point_key='%s' AND enabled=1 LIMIT 1",
|
||||
locationKey.c_str());
|
||||
|
||||
if (!result)
|
||||
{
|
||||
handler->PSendSysMessage("Unknown/enabled location '%s'.", locationKey.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
Field* f = result->Fetch();
|
||||
uint32 const mapId = f[0].GetUInt32();
|
||||
if (mapId != bot->GetMapId())
|
||||
{
|
||||
handler->PSendSysMessage("Navtest requires same map: bot=%u location=%u.", bot->GetMapId(), mapId);
|
||||
return true;
|
||||
}
|
||||
|
||||
target.Relocate(f[1].GetFloat(), f[2].GetFloat(), f[3].GetFloat(), f[4].GetFloat());
|
||||
}
|
||||
else
|
||||
{
|
||||
Player* gm = handler->GetSession()->GetPlayer();
|
||||
if (!gm || !gm->IsInWorld())
|
||||
return true;
|
||||
if (gm->GetMapId() != bot->GetMapId())
|
||||
{
|
||||
handler->PSendSysMessage("Navtest requires same map: bot=%u GM=%u.", bot->GetMapId(), gm->GetMapId());
|
||||
return true;
|
||||
}
|
||||
target.Relocate(gm->GetPosition());
|
||||
}
|
||||
|
||||
ClientlessPathDiagnostics nav;
|
||||
sClientlessTravelMgr->AnalyzePath(bot, target, nav);
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"Navtest GUID %u map=%u start=(%.3f %.3f %.3f) target=(%.3f %.3f %.3f)",
|
||||
characterGuid, bot->GetMapId(),
|
||||
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
target.GetPositionX(), target.GetPositionY(), target.GetPositionZ());
|
||||
if (locationMode)
|
||||
handler->PSendSysMessage(" location='%s'", locationKey.c_str());
|
||||
PrintClientlessPathDiagnostics(handler, characterGuid, nav);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleClientlessTravelInfo(ChatHandler* handler, char const* args)
|
||||
{
|
||||
uint32 characterGuid = 0;
|
||||
@@ -1473,6 +1606,7 @@ static bool HandleClientlessTravelReload(ChatHandler* handler, char const* /*arg
|
||||
status->RemainingDistance,
|
||||
status->RepathCount,
|
||||
status->NoProgressMs);
|
||||
PrintClientlessPathDiagnostics(handler, characterGuid, status->Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1791,6 +1925,8 @@ static bool HandleClientlessTravelReload(ChatHandler* handler, char const* /*arg
|
||||
{ "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessInfo, "" },
|
||||
{ "travelto", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessTravelTo, "" },
|
||||
{ "travelinfo", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessTravelInfo, "" },
|
||||
{ "navtest", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessNavTest, "" },
|
||||
{ "navinfo", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessNavInfo, "" },
|
||||
{ "perceive", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessPerceive, "" },
|
||||
{ "actioninfo", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessActionInfo, "" },
|
||||
{ "pointreload", rbac::RBAC_PERM_COMMAND_GM, false, &HandleClientlessPointReload, "" },
|
||||
|
||||
Reference in new issue
Block a user