refactor(Core/Travel): Simplify transport config to TransportSkipRide bool; drop mode-0 deck-walk approximations

This commit is contained in:
bash 2026-05-31 15:38:45 +02:00
parent f90f365e94
commit 6079863fce
5 changed files with 59 additions and 166 deletions

View File

@ -1038,14 +1038,15 @@ AiPlayerbot.EnableNewRpgStrategy = 1
# Default: 0 (disabled) # Default: 0 (disabled)
AiPlayerbot.EnableTravelNodes = 0 AiPlayerbot.EnableTravelNodes = 0
# Transport handling mode (travel node graph only): # Transport ride mode (travel node graph only):
# 0 = bot walks to dock, boards transport, rides, walks off (fully physical) # 0 = bot walks to dock, teleport-snaps onto transport, rides, teleport-snaps off
# 1 = same as 0, but if walk-on/off fails the boarding teleports the bot # 1 = bot teleports directly across the transport route, skipping the ride
# 2 = bot teleports across the route, skipping the ride entirely # Default 0 is the visible behavior. Set to 1 for invisible/random-bot mass
# Reference default is 0; raise only if invisible/random-bot mass travel # travel performance — the bot never actually boards anything.
# performance becomes an issue. # (AC has no transport-surface mmap, so an on-deck walking mode can't be
# faithfully implemented; the on-board phase always teleport-snaps.)
# Default: 0 # Default: 0
AiPlayerbot.TransportTeleportType = 0 AiPlayerbot.TransportSkipRide = 0
# Control probability weights for RPG status of bots. Takes effect only when the status meets its premise. # Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.
# Sum of weights need not be 100. Set to 0 to disable the status. # Sum of weights need not be 100. Set to 0 to disable the status.

View File

@ -3145,47 +3145,20 @@ bool MovementAction::WaitForTransport()
return false; return false;
// Disembark: head is the transport node where we should get off, // Disembark: head is the transport node where we should get off,
// next is the world-position dock to land at. Reference UseTransport // next is the world-position dock to land at.
// with doTeleport=(transportTeleportType > 0) routes to either
// MoveOffTransport-teleport (mode 1+) or MoveOffTransport-walk (mode 0).
PathNodePoint const& dock = path[0];
if (path.size() < 2) if (path.size() < 2)
return true; // no telePoint to land at; keep waiting return true; // no telePoint to land at; keep waiting
PathNodePoint const& tele = path[1]; PathNodePoint const& tele = path[1];
(void)dock; // kept for reference parity (UseTransport signature)
transport->RemovePassenger(bot); transport->RemovePassenger(bot);
bot->StopMovingOnCurrentPos();
if (sPlayerbotAIConfig.transportTeleportType > 0) bool const teleported = bot->TeleportTo(tele.point.GetMapId(),
{ tele.point.GetPositionX(),
// Mode 1+: teleport directly to exit. tele.point.GetPositionY(),
bot->StopMovingOnCurrentPos(); tele.point.GetPositionZ(),
bool const teleported = bot->TeleportTo(tele.point.GetMapId(), bot->GetOrientation());
tele.point.GetPositionX(), if (!teleported)
tele.point.GetPositionY(), return true; // try again next tick
tele.point.GetPositionZ(),
bot->GetOrientation());
if (!teleported)
return true; // try again next tick
lastMove.lastTransportEntry = 0;
return false;
}
// Mode 0: NearTeleportTo current world position (drops bot off
// transport plane to world coords without unloading client), then
// walk to the exit position. Matches reference MoveOffTransport
// with doTeleport=false.
bot->NearTeleportTo(bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), bot->GetOrientation());
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->Clear();
mm->MovePoint(0, tele.point.GetPositionX(),
tele.point.GetPositionY(),
tele.point.GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, true, false);
}
lastMove.lastTransportEntry = 0; lastMove.lastTransportEntry = 0;
return false; return false;
@ -3274,10 +3247,7 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
case PathNodeType::NODE_TRANSPORT: case PathNodeType::NODE_TRANSPORT:
{ {
// Disembark: head is a transport node and bot is on one. // Disembark: head is a transport node and bot is on one.
// Reference dispatch: MoveOffTransport(exit, doTeleport) where // RemovePassenger + TeleportTo the next-step world position.
// doTeleport = (transportTeleportType > 0). Mode 1+ teleports
// straight to exit; mode 0 NearTeleports off transport plane
// then walks to exit.
if (!hasNext) if (!hasNext)
return false; return false;
@ -3287,35 +3257,14 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
PathNodePoint const& dst = path[1]; PathNodePoint const& dst = path[1];
transport->RemovePassenger(bot); transport->RemovePassenger(bot);
bot->StopMovingOnCurrentPos();
if (sPlayerbotAIConfig.transportTeleportType > 0) bool const teleported = bot->TeleportTo(dst.point.GetMapId(),
{ dst.point.GetPositionX(),
bot->StopMovingOnCurrentPos(); dst.point.GetPositionY(),
bool const teleported = bot->TeleportTo(dst.point.GetMapId(), dst.point.GetPositionZ(),
dst.point.GetPositionX(), bot->GetOrientation());
dst.point.GetPositionY(),
dst.point.GetPositionZ(),
bot->GetOrientation());
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0;
return teleported;
}
// Mode 0: NearTeleportTo current world pos (drops bot off
// transport plane without unloading the client), then walk
// to the exit.
bot->NearTeleportTo(bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), bot->GetOrientation());
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->Clear();
mm->MovePoint(0, dst.point.GetPositionX(),
dst.point.GetPositionY(),
dst.point.GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, true, false);
}
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0; AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0;
return true; return teleported;
} }
default: default:
@ -3527,87 +3476,29 @@ bool MovementAction::BoardTransport(Transport* transport)
return true; return true;
} }
// Teleport-onto mode (transportTeleportType >= 1): skip the walk-toward // Bot off transport: find a boarding edge and teleport-snap directly
// phase and jump directly to a boarding edge. Matches reference's // onto it, then AddPassenger. We can't walk on the deck (no transport-
// MoveOnTransport(doTeleport=true) — PlayerRelocation + AddPassenger. // surface mmap), so the snap-board is the only universal approach.
if (sPlayerbotAIConfig.transportTeleportType >= 1)
{
float edgeX, edgeY, edgeZ;
if (FindBoardingPointOnTransport(map, transport, transport,
transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
edgeX, edgeY, edgeZ))
{
if (bot->TeleportTo(map->GetId(), edgeX, edgeY, edgeZ, bot->GetOrientation()))
{
transport->AddPassenger(bot, true);
bot->StopMovingOnCurrentPos();
EmitDebugMove("Transport:board", "teleport-skip-walk",
edgeX, edgeY, edgeZ);
return true;
}
}
// Fall through to mode-0 if no boarding point — better than refusing.
}
// Mode 0: reference MoveOnTransport(doTeleport=false) — AddPassenger
// FIRST (snaps bot onto transport plane), then walk a path on deck.
// We don't have RandomPointOnTrans / transport-surface mmap, so when
// the bot is within INTERACTION_DISTANCE of the transport's boarding
// edge we approximate: AddPassenger + MovePoint toward the transport
// center as a "deck destination." Bot motion is then transport-
// relative because of the AddPassenger.
float boardX = transport->GetPositionX();
float boardY = transport->GetPositionY();
float boardZ = transport->GetPositionZ();
float edgeX, edgeY, edgeZ; float edgeX, edgeY, edgeZ;
bool haveEdge = FindBoardingPointOnTransport(map, transport, transport, if (!FindBoardingPointOnTransport(map, transport, transport,
transport->GetPositionX(), transport->GetPositionY(), transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(), bot->GetPositionX(), bot->GetPositionY(), transport->GetPositionZ(),
bot->GetPositionZ(), edgeX, edgeY, edgeZ); bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
if (haveEdge) edgeX, edgeY, edgeZ))
{ {
boardX = edgeX; // No boarding edge found — wait a tick. Caller's WaitForReach
boardY = edgeY; // upstream throttles the retry.
boardZ = edgeZ; EmitDebugMove("Transport:board", "no-edge",
transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ());
return false;
} }
float const dxBoard = bot->GetPositionX() - boardX; if (!bot->TeleportTo(map->GetId(), edgeX, edgeY, edgeZ, bot->GetOrientation()))
float const dyBoard = bot->GetPositionY() - boardY; return false;
bool const nearBoardingPoint =
(dxBoard * dxBoard + dyBoard * dyBoard) <
INTERACTION_DISTANCE * INTERACTION_DISTANCE;
if (nearBoardingPoint) transport->AddPassenger(bot, true);
{ bot->StopMovingOnCurrentPos();
// Snap onto transport plane, then walk a short deck path so the EmitDebugMove("Transport:board", "snap", edgeX, edgeY, edgeZ);
// bot is visibly on the boat (mirrors reference mode-0 deck walk). return true;
transport->AddPassenger(bot, true);
if (MotionMaster* mm = bot->GetMotionMaster())
{
if (!bot->IsStandState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
mm->Clear();
mm->MovePoint(0, transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, false, false);
}
EmitDebugMove("Transport:board", "snap-and-deck-walk",
boardX, boardY, boardZ);
return true;
}
// Too far to snap — walk toward boarding edge.
if (MotionMaster* mm = bot->GetMotionMaster())
{
if (!bot->IsStandState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
mm->MovePoint(0, boardX, boardY, boardZ,
FORCED_MOVEMENT_NONE, 0.0f, 0.0f, false, false);
EmitDebugMove("Transport:walk", "approach-boarding", boardX, boardY, boardZ);
}
return false;
} }

View File

@ -927,9 +927,9 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
return true; return true;
} }
// Walk-on / teleport-to-transport boarding mode (modes 0 and 1). // Board-and-ride mode (transportSkipRide == false). Cut to dock if
// Cut to dock if off-transport, traverse to disembark if on-transport. // off-transport, traverse to disembark if on-transport.
if (sPlayerbotAIConfig.transportTeleportType < 2 && if (!sPlayerbotAIConfig.transportSkipRide &&
startP->type == PathNodeType::NODE_TRANSPORT) startP->type == PathNodeType::NODE_TRANSPORT)
{ {
uint32 const entry = nextP->entry; uint32 const entry = nextP->entry;
@ -954,11 +954,11 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
} }
} }
// Teleport-across mode (mode 2): bot is approaching a transport // Skip-ride mode (transportSkipRide == true): bot is approaching a
// node — walk forward to find the first non-transport node (the // transport node — walk forward to find the first non-transport node
// disembark side), cut to prevP (last transport node) so // (the disembark side), cut to prevP (last transport node) so
// HandleSpecialMovement teleports the bot across directly. // HandleSpecialMovement teleports the bot across directly.
if (sPlayerbotAIConfig.transportTeleportType == 2 && if (sPlayerbotAIConfig.transportSkipRide &&
nextP->type == PathNodeType::NODE_TRANSPORT) nextP->type == PathNodeType::NODE_TRANSPORT)
{ {
for (auto p = std::next(startP); p != fullPath.end(); ++p) for (auto p = std::next(startP); p != fullPath.end(); ++p)

View File

@ -88,7 +88,7 @@ bool PlayerbotAIConfig::Initialize()
farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f); farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f);
sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 100.0f); sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 100.0f);
transportTeleportType = sConfigMgr->GetOption<uint32>("AiPlayerbot.TransportTeleportType", 0); transportSkipRide = sConfigMgr->GetOption<bool>("AiPlayerbot.TransportSkipRide", false);
spellDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SpellDistance", 28.5f); spellDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SpellDistance", 28.5f);
shootDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ShootDistance", 5.0f); shootDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ShootDistance", 5.0f);
healDistance = sConfigMgr->GetOption<float>("AiPlayerbot.HealDistance", 38.5f); healDistance = sConfigMgr->GetOption<float>("AiPlayerbot.HealDistance", 38.5f);

View File

@ -93,11 +93,12 @@ public:
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat; bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime, uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime,
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay; dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
// Transport handling mode (matches reference `transportTeleportType`): // Transport handling:
// 0 = walk on board, walk off (default, fully physical) // false (default) = teleport-board, ride the transport, teleport-disembark
// 1 = walk on board, but UseTransport teleports on/off if walk fails // true = skip the ride entirely (teleport directly across)
// 2 = skip the ride entirely — teleport directly across the route // AC has no transport-surface mmap so an in-deck walking mode can't be
uint32 transportTeleportType; // faithfully implemented — the on-board phase always teleport-snaps.
bool transportSkipRide;
bool dynamicReactDelay; bool dynamicReactDelay;
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance, float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance, tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance,