|
5 | 5 |
|
6 | 6 | #include "FollowActions.h" |
7 | 7 |
|
| 8 | +#include <algorithm> |
| 9 | +#include <cmath> |
| 10 | +#include <array> |
| 11 | + |
8 | 12 | #include "Event.h" |
9 | 13 | #include "Formations.h" |
10 | 14 | #include "LastMovementValue.h" |
| 15 | +#include "MotionMaster.h" |
11 | 16 | #include "PlayerbotAI.h" |
12 | 17 | #include "Playerbots.h" |
13 | 18 | #include "ServerFacade.h" |
| 19 | +#include "Transport.h" |
| 20 | +#include "Map.h" |
| 21 | + |
| 22 | +namespace |
| 23 | +{ |
| 24 | + Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z) |
| 25 | + { |
| 26 | + if (!map || !ref) |
| 27 | + return nullptr; |
| 28 | + |
| 29 | + std::array<float, 4> const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f }; |
| 30 | + for (float const pz : probes) |
| 31 | + { |
| 32 | + if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref)) |
| 33 | + return t; |
| 34 | + } |
| 35 | + |
| 36 | + return nullptr; |
| 37 | + } |
| 38 | + |
| 39 | + // Attempts to find a point on the leader's transport that is closer to the bot, |
| 40 | + // by probing along the segment from master -> bot and returning the last point |
| 41 | + // that is still detected as being on the expected transport. |
| 42 | + bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref, |
| 43 | + float masterX, float masterY, float masterZ, |
| 44 | + float botX, float botY, float botZ, |
| 45 | + float& outX, float& outY, float& outZ) |
| 46 | + { |
| 47 | + if (!map || !expectedTransport || !ref) |
| 48 | + return false; |
| 49 | + |
| 50 | + uint32 const phaseMask = ref->GetPhaseMask(); |
| 51 | + |
| 52 | + // Ensure master is actually detected on that transport (tolerant). |
| 53 | + if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport) |
| 54 | + return false; |
| 55 | + |
| 56 | + // The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z. |
| 57 | + float const probeZ = std::max(masterZ, botZ); |
| 58 | + |
| 59 | + // Adaptive step count: small platforms need tighter sampling. |
| 60 | + float const dx2 = botX - masterX; |
| 61 | + float const dy2 = botY - masterY; |
| 62 | + float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2); |
| 63 | + int32 const steps = std::clamp(static_cast<int32>(dist2d / 0.75f), 10, 28); |
| 64 | + |
| 65 | + float const dx = (botX - masterX) / static_cast<float>(steps); |
| 66 | + float const dy = (botY - masterY) / static_cast<float>(steps); |
| 67 | + |
| 68 | + // Master must actually be on the expected transport for this to work. |
| 69 | + if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport) |
| 70 | + return false; |
| 71 | + |
| 72 | + float lastX = masterX; |
| 73 | + float lastY = masterY; |
| 74 | + bool found = false; |
| 75 | + |
| 76 | + for (int32 i = 1; i <= steps; ++i) |
| 77 | + { |
| 78 | + float const px = masterX + dx * i; |
| 79 | + float const py = masterY + dy * i; |
| 80 | + |
| 81 | + Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ); |
| 82 | + if (t != expectedTransport) |
| 83 | + break; |
| 84 | + |
| 85 | + lastX = px; |
| 86 | + lastY = py; |
| 87 | + found = true; |
| 88 | + } |
| 89 | + |
| 90 | + if (!found) |
| 91 | + return false; |
| 92 | + |
| 93 | + outX = lastX; |
| 94 | + outY = lastY; |
| 95 | + outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat |
| 96 | + return true; |
| 97 | + } |
| 98 | +} |
14 | 99 |
|
15 | 100 | bool FollowAction::Execute(Event /*event*/) |
16 | 101 | { |
17 | 102 | Formation* formation = AI_VALUE(Formation*, "formation"); |
18 | 103 | std::string const target = formation->GetTargetName(); |
19 | 104 |
|
| 105 | + // Transport handling for moving transports only (boats/zeppelins). |
| 106 | + Player* master = botAI->GetMaster(); |
| 107 | + if (master && master->IsInWorld() && bot->IsInWorld() && bot->GetMapId() == master->GetMapId()) |
| 108 | + { |
| 109 | + Map* map = master->GetMap(); |
| 110 | + uint32 const mapId = bot->GetMapId(); |
| 111 | + Transport* transport = nullptr; |
| 112 | + bool masterOnTransport = false; |
| 113 | + |
| 114 | + if (master->GetTransport()) |
| 115 | + { |
| 116 | + transport = master->GetTransport(); |
| 117 | + masterOnTransport = true; |
| 118 | + } |
| 119 | + else if (map) |
| 120 | + { |
| 121 | + transport = GetTransportForPosTolerant(map, master, master->GetPhaseMask(), |
| 122 | + master->GetPositionX(), master->GetPositionY(), master->GetPositionZ()); |
| 123 | + masterOnTransport = (transport != nullptr); |
| 124 | + } |
| 125 | + |
| 126 | + // Ignore static transports (elevators/trams): only keep boats/zeppelins here. |
| 127 | + if (transport && transport->IsStaticTransport()) |
| 128 | + transport = nullptr; |
| 129 | + |
| 130 | + if (transport && map && bot->GetTransport() != transport) |
| 131 | + { |
| 132 | + float const botProbeZ = std::max(bot->GetPositionZ(), transport->GetPositionZ()); |
| 133 | + Transport* botSurfaceTransport = GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(), |
| 134 | + bot->GetPositionX(), bot->GetPositionY(), botProbeZ); |
| 135 | + |
| 136 | + if (botSurfaceTransport == transport) |
| 137 | + { |
| 138 | + transport->AddPassenger(bot, true); |
| 139 | + bot->StopMovingOnCurrentPos(); |
| 140 | + return true; |
| 141 | + } |
| 142 | + |
| 143 | + float const boardingAssistDistance = 60.0f; |
| 144 | + float const dist2d = ServerFacade::instance().GetDistance2d(bot, master); |
| 145 | + bool const inAssist = ServerFacade::instance().IsDistanceLessOrEqualThan(dist2d, boardingAssistDistance); |
| 146 | + |
| 147 | + if (inAssist) |
| 148 | + { |
| 149 | + float destX = masterOnTransport ? master->GetPositionX() : transport->GetPositionX(); |
| 150 | + float destY = masterOnTransport ? master->GetPositionY() : transport->GetPositionY(); |
| 151 | + float destZ = masterOnTransport ? master->GetPositionZ() : transport->GetPositionZ(); |
| 152 | + float edgeX = 0.0f; |
| 153 | + float edgeY = 0.0f; |
| 154 | + float edgeZ = 0.0f; |
| 155 | + |
| 156 | + if (masterOnTransport && |
| 157 | + FindBoardingPointOnTransport(map, transport, master, |
| 158 | + master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), |
| 159 | + bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), |
| 160 | + edgeX, edgeY, edgeZ)) |
| 161 | + { |
| 162 | + destX = edgeX; |
| 163 | + destY = edgeY; |
| 164 | + destZ = edgeZ; |
| 165 | + } |
| 166 | + |
| 167 | + MovementPriority const priority = botAI->GetState() == BOT_STATE_COMBAT |
| 168 | + ? MovementPriority::MOVEMENT_COMBAT |
| 169 | + : MovementPriority::MOVEMENT_NORMAL; |
| 170 | + |
| 171 | + bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ); |
| 172 | + bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ); |
| 173 | + bool const waiting = IsWaitingForLastMove(priority); |
| 174 | + |
| 175 | + if (movingAllowed && !dupMove && !waiting) |
| 176 | + { |
| 177 | + if (bot->IsSitState()) |
| 178 | + bot->SetStandState(UNIT_STAND_STATE_STAND); |
| 179 | + |
| 180 | + if (bot->IsNonMeleeSpellCast(true)) |
| 181 | + { |
| 182 | + bot->CastStop(); |
| 183 | + botAI->InterruptSpell(); |
| 184 | + } |
| 185 | + |
| 186 | + if (MotionMaster* mm = bot->GetMotionMaster()) |
| 187 | + { |
| 188 | + mm->MovePoint( |
| 189 | + /*id*/ 0, |
| 190 | + /*coords*/ destX, destY, destZ, |
| 191 | + /*forcedMovement*/ FORCED_MOVEMENT_NONE, |
| 192 | + /*speed*/ 0.0f, |
| 193 | + /*orientation*/ 0.0f, |
| 194 | + /*generatePath*/ false, |
| 195 | + /*forceDestination*/ false); |
| 196 | + } |
| 197 | + else |
| 198 | + return false; |
| 199 | + |
| 200 | + float delay = 1000.0f * MoveDelay(bot->GetExactDist(destX, destY, destZ)); |
| 201 | + delay = std::clamp(delay, 0.0f, static_cast<float>(sPlayerbotAIConfig.maxWaitForMove)); |
| 202 | + |
| 203 | + AI_VALUE(LastMovement&, "last movement") |
| 204 | + .Set(mapId, destX, destY, destZ, bot->GetOrientation(), delay, priority); |
| 205 | + ClearIdleState(); |
| 206 | + return true; |
| 207 | + } |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | + // end unified transport handling |
| 212 | + |
20 | 213 | bool moved = false; |
21 | 214 | if (!target.empty()) |
22 | 215 | { |
|
0 commit comments