Core/Transports: Path generation rewrite v2

* No TaxiPathNode row is skipped anymore (events/delays on first node now work)
* Fixed transport animation synchronization with client
* Fixed stoppable transports randomly resuming their path after relogging

Closes #27985
This commit is contained in:
Shauren
2022-06-07 16:02:51 +02:00
parent b9353041a6
commit a2c1b699e6
7 changed files with 505 additions and 481 deletions

View File

@@ -26,7 +26,6 @@
#include "Player.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Transport.h"
#include "World.h"
#include "WorldSession.h"
@@ -86,21 +85,12 @@ public:
dynFlags |= GO_DYNFLAG_LO_SPARKLE | GO_DYNFLAG_LO_HIGHLIGHT;
break;
case GAMEOBJECT_TYPE_TRANSPORT:
case GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT:
{
dynFlags = dynamicFlags & 0xFFFF;
pathProgress = dynamicFlags >> 16;
break;
}
case GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT:
{
Transport const* transport = gameObject->ToTransport();
if (uint32 transportPeriod = transport->GetTransportPeriod())
{
float timer = float(transport->GetTimer() % transportPeriod);
pathProgress = uint16(timer / float(transportPeriod) * 65535.0f);
}
break;
}
case GAMEOBJECT_TYPE_CAPTURE_POINT:
if (!gameObject->CanInteractWithCapturePoint(receiver))
dynFlags |= GO_DYNFLAG_LO_NO_INTERACT;
@@ -111,7 +101,7 @@ public:
break;
}
dynamicFlags = (pathProgress << 16) | dynFlags;
dynamicFlags = (uint32(pathProgress) << 16) | uint32(dynFlags);
}
return dynamicFlags;

View File

@@ -19,8 +19,10 @@
#include "Cell.h"
#include "CellImpl.h"
#include "Common.h"
#include "DB2Stores.h"
#include "GameEventSender.h"
#include "GameObjectAI.h"
#include "GameTime.h"
#include "Log.h"
#include "MapManager.h"
#include "ObjectMgr.h"
@@ -31,6 +33,7 @@
#include "Totem.h"
#include "UpdateData.h"
#include "Vehicle.h"
#include <boost/dynamic_bitset.hpp>
#include <G3D/Vector3.h>
#include <sstream>
@@ -90,9 +93,8 @@ void TransportBase::UpdatePassengerPosition(Map* map, WorldObject* passenger, fl
}
Transport::Transport() : GameObject(),
_transportInfo(nullptr), _pathProgress(0), _isMoving(true), _pendingStop(false),
_triggeredArrivalEvent(false), _triggeredDepartureEvent(false),
_passengerTeleportItr(_passengers.begin()), _delayedAddModel(false), _delayedTeleport(false)
_transportInfo(nullptr), _movementState(TransportMovementState::Moving), _eventsToTrigger(std::make_unique<boost::dynamic_bitset<uint8>>()),
_currentPathLeg(0), _pathProgress(0), _delayedAddModel(false)
{
m_updateFlag.ServerTime = true;
m_updateFlag.Stationary = true;
@@ -136,12 +138,7 @@ bool Transport::Create(ObjectGuid::LowType guidlow, uint32 entry, uint32 mapid,
}
_transportInfo = tInfo;
// initialize waypoints
_nextFrame = tInfo->keyFrames.begin();
_currentFrame = _nextFrame++;
_triggeredArrivalEvent = false;
_triggeredDepartureEvent = false;
_eventsToTrigger->resize(tInfo->Events.size(), true);
if (GameObjectOverride const* goOverride = GetGameObjectOverride())
{
@@ -149,14 +146,16 @@ bool Transport::Create(ObjectGuid::LowType guidlow, uint32 entry, uint32 mapid,
ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
}
_pathProgress = 0;
_pathProgress = !goinfo->moTransport.allowstopping ? getMSTime() /*might be called before world update loop begins, don't use GameTime*/ % tInfo->TotalPathTime : 0;
SetPathProgressForClient(float(_pathProgress) / float(tInfo->TotalPathTime));
SetObjectScale(goinfo->size);
SetPeriod(tInfo->pathTime);
SetPeriod(tInfo->TotalPathTime);
SetEntry(goinfo->entry);
SetDisplayId(goinfo->displayId);
SetGoState(!goinfo->moTransport.allowstopping ? GO_STATE_READY : GO_STATE_ACTIVE);
SetGoType(GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT);
SetGoAnimProgress(animprogress);
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpawnTrackingStateAnimID), sDB2Manager.GetEmptyAnimStateID());
SetName(goinfo->name);
SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f);
SetParentRotation(QuaternionData());
@@ -179,77 +178,93 @@ void Transport::CleanupsBeforeDelete(bool finalCleanup /*= true*/)
void Transport::Update(uint32 diff)
{
uint32 const positionUpdateDelay = 200;
constexpr Milliseconds positionUpdateDelay = 200ms;
if (AI())
AI()->UpdateAI(diff);
else if (!AIM_Initialize())
TC_LOG_ERROR("entities.transport", "Could not initialize GameObjectAI for Transport");
if (GetKeyFrames().size() <= 1)
return;
sScriptMgr->OnTransportUpdate(this, diff);
if (IsMoving() || !_pendingStop)
_positionChangeTimer.Update(diff);
uint32 cycleId = _pathProgress / GetTransportPeriod();
if (!GetGOInfo()->moTransport.allowstopping)
_pathProgress = GameTime::GetGameTimeMS();
else if (!_requestStopTimestamp || _requestStopTimestamp > _pathProgress + diff)
_pathProgress += diff;
else
_pathProgress = *_requestStopTimestamp;
if (_pathProgress / GetTransportPeriod() != cycleId)
{
// reset cycle
_eventsToTrigger->set();
}
SetPathProgressForClient(float(_pathProgress) / float(GetTransportPeriod()));
uint32 timer = _pathProgress % GetTransportPeriod();
bool justStopped = false;
// Set current waypoint
// Desired outcome: _currentFrame->DepartureTime < timer < _nextFrame->ArriveTime
// ... arrive | ... delay ... | departure
// event / event /
for (;;)
size_t eventToTriggerIndex = _eventsToTrigger->find_first();
if (eventToTriggerIndex != boost::dynamic_bitset<uint8>::npos)
{
if (timer >= _currentFrame->ArriveTime)
while (eventToTriggerIndex < _transportInfo->Events.size() && _transportInfo->Events[eventToTriggerIndex].Timestamp < timer)
{
if (!_triggeredArrivalEvent)
{
DoEventIfAny(*_currentFrame, false);
_triggeredArrivalEvent = true;
}
GameEvents::Trigger(_transportInfo->Events[eventToTriggerIndex].EventId, this, this);
_eventsToTrigger->set(eventToTriggerIndex, false);
++eventToTriggerIndex;
}
}
if (timer < _currentFrame->DepartureTime)
TransportMovementState moveState;
size_t legIndex;
if (Optional<Position> newPosition = _transportInfo->ComputePosition(timer, &moveState, &legIndex))
{
bool justStopped = _movementState == TransportMovementState::Moving && moveState != TransportMovementState::Moving;
_movementState = moveState;
if (justStopped)
{
if (_requestStopTimestamp && GetGoState() != GO_STATE_READY)
{
justStopped = IsMoving();
SetMoving(false);
if (_pendingStop && GetGoState() != GO_STATE_READY)
{
SetGoState(GO_STATE_READY);
_pathProgress = (_pathProgress / GetTransportPeriod());
_pathProgress *= GetTransportPeriod();
_pathProgress += _currentFrame->ArriveTime;
}
break; // its a stop frame and we are waiting
SetGoState(GO_STATE_READY);
SetDynamicFlag(GO_DYNFLAG_LO_STOPPED);
}
}
if (timer >= _currentFrame->DepartureTime && !_triggeredDepartureEvent)
if (legIndex != _currentPathLeg)
{
DoEventIfAny(*_currentFrame, true); // departure event
_triggeredDepartureEvent = true;
_currentPathLeg = legIndex;
TeleportTransport(_transportInfo->PathLegs[legIndex].MapId, newPosition->GetPositionX(), newPosition->GetPositionY(), newPosition->GetPositionZ(), newPosition->GetOrientation());
return;
}
// not waiting anymore
SetMoving(true);
// set position
if (_positionChangeTimer.Passed())
{
_positionChangeTimer.Reset(positionUpdateDelay);
if (_movementState == TransportMovementState::Moving || justStopped)
UpdatePosition(newPosition->GetPositionX(), newPosition->GetPositionY(), newPosition->GetPositionZ(), newPosition->GetOrientation());
else
{
/* There are four possible scenarios that trigger loading/unloading passengers:
1. transport moves from inactive to active grid
2. the grid that transport is currently in becomes active
3. transport moves from active to inactive grid
4. the grid that transport is currently in unloads
*/
bool gridActive = GetMap()->IsGridLoaded(GetPositionX(), GetPositionY());
// Enable movement
if (GetGOInfo()->moTransport.allowstopping)
SetGoState(GO_STATE_ACTIVE);
if (timer >= _currentFrame->DepartureTime && timer < _currentFrame->NextArriveTime)
break; // found current waypoint
MoveToNextWaypoint();
sScriptMgr->OnRelocate(this, _currentFrame->Node->NodeIndex, _currentFrame->Node->ContinentID, _currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z);
TC_LOG_DEBUG("entities.transport", "Transport %u (%s) moved to node %u %u %f %f %f", GetEntry(), GetName().c_str(), _currentFrame->Node->NodeIndex, _currentFrame->Node->ContinentID, _currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z);
// Departure event
if (_currentFrame->IsTeleportFrame())
if (TeleportTransport(_nextFrame->Node->ContinentID, _nextFrame->Node->Loc.X, _nextFrame->Node->Loc.Y, _nextFrame->Node->Loc.Z, _nextFrame->InitialOrientation))
return; // Update more in new map thread
if (_staticPassengers.empty() && gridActive) // 2.
LoadStaticPassengers();
else if (!_staticPassengers.empty() && !gridActive)
// 4. - if transports stopped on grid edge, some passengers can remain in active grids
// unload all static passengers otherwise passengers won't load correctly when the grid that transport is currently in becomes active
UnloadStaticPassengers();
}
}
}
// Add model to map after we are fully done with moving maps
@@ -259,49 +274,10 @@ void Transport::Update(uint32 diff)
if (m_model)
GetMap()->InsertGameObjectModel(*m_model);
}
// Set position
_positionChangeTimer.Update(diff);
if (_positionChangeTimer.Passed())
{
_positionChangeTimer.Reset(positionUpdateDelay);
if (IsMoving())
{
float t = !justStopped ? CalculateSegmentPos(float(timer) * 0.001f) : 1.0f;
G3D::Vector3 pos, dir;
_currentFrame->Spline->evaluate_percent(_currentFrame->Index, t, pos);
_currentFrame->Spline->evaluate_derivative(_currentFrame->Index, t, dir);
UpdatePosition(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI));
}
else if (justStopped)
UpdatePosition(_currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z, _currentFrame->InitialOrientation);
else
{
/* There are four possible scenarios that trigger loading/unloading passengers:
1. transport moves from inactive to active grid
2. the grid that transport is currently in becomes active
3. transport moves from active to inactive grid
4. the grid that transport is currently in unloads
*/
bool gridActive = GetMap()->IsGridLoaded(GetPositionX(), GetPositionY());
if (_staticPassengers.empty() && gridActive) // 2.
LoadStaticPassengers();
else if (!_staticPassengers.empty() && !gridActive)
// 4. - if transports stopped on grid edge, some passengers can remain in active grids
// unload all static passengers otherwise passengers won't load correctly when the grid that transport is currently in becomes active
UnloadStaticPassengers();
}
}
sScriptMgr->OnTransportUpdate(this, diff);
}
void Transport::DelayedUpdate(uint32 /*diff*/)
{
if (GetKeyFrames().size() <= 1)
return;
DelayedTeleportTransport();
}
@@ -323,23 +299,7 @@ void Transport::AddPassenger(WorldObject* passenger)
Transport* Transport::RemovePassenger(WorldObject* passenger)
{
bool erased = false;
if (_passengerTeleportItr != _passengers.end())
{
PassengerSet::iterator itr = _passengers.find(passenger);
if (itr != _passengers.end())
{
if (itr == _passengerTeleportItr)
++_passengerTeleportItr;
_passengers.erase(itr);
erased = true;
}
}
else
erased = _passengers.erase(passenger) > 0;
if (erased || _staticPassengers.erase(passenger)) // static passenger can remove itself in case of grid unload
if (_passengers.erase(passenger) || _staticPassengers.erase(passenger)) // static passenger can remove itself in case of grid unload
{
passenger->SetTransport(nullptr);
passenger->m_movementInfo.transport.Reset();
@@ -574,6 +534,8 @@ int32 Transport::GetMapIdForSpawning() const
void Transport::UpdatePosition(float x, float y, float z, float o)
{
sScriptMgr->OnRelocate(this, GetMapId(), x, y, z);
bool newActive = GetMap()->IsGridLoaded(x, y);
Cell oldCell(GetPositionX(), GetPositionY());
@@ -633,50 +595,16 @@ void Transport::EnableMovement(bool enabled)
if (!GetGOInfo()->moTransport.allowstopping)
return;
_pendingStop = !enabled;
}
void Transport::MoveToNextWaypoint()
{
// Clear events flagging
_triggeredArrivalEvent = false;
_triggeredDepartureEvent = false;
// Set frames
_currentFrame = _nextFrame++;
if (_nextFrame == GetKeyFrames().end())
_nextFrame = GetKeyFrames().begin();
}
float Transport::CalculateSegmentPos(float now)
{
KeyFrame const& frame = *_currentFrame;
const float speed = float(m_goInfo->moTransport.moveSpeed);
const float accel = float(m_goInfo->moTransport.accelRate);
float timeSinceStop = frame.TimeFrom + (now - (1.0f/IN_MILLISECONDS) * frame.DepartureTime);
float timeUntilStop = frame.TimeTo - (now - (1.0f/IN_MILLISECONDS) * frame.DepartureTime);
float segmentPos, dist;
float accelTime = _transportInfo->accelTime;
float accelDist = _transportInfo->accelDist;
// calculate from nearest stop, less confusing calculation...
if (timeSinceStop < timeUntilStop)
if (!enabled)
{
if (timeSinceStop < accelTime)
dist = 0.5f * accel * timeSinceStop * timeSinceStop;
else
dist = accelDist + (timeSinceStop - accelTime) * speed;
segmentPos = dist - frame.DistSinceStop;
_requestStopTimestamp = (_pathProgress / GetTransportPeriod()) * GetTransportPeriod() + _transportInfo->GetNextPauseWaypointTimestamp(_pathProgress);
}
else
{
if (timeUntilStop < _transportInfo->accelTime)
dist = 0.5f * accel * timeUntilStop * timeUntilStop;
else
dist = accelDist + (timeUntilStop - accelTime) * speed;
segmentPos = frame.DistUntilStop - dist;
_requestStopTimestamp.reset();
SetGoState(GO_STATE_ACTIVE);
RemoveDynamicFlag(GO_DYNFLAG_LO_STOPPED);
}
return segmentPos / frame.NextDistFromPrev;
}
bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z, float o)
@@ -685,7 +613,7 @@ bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z, fl
if (oldMap->GetId() != newMapid)
{
_delayedTeleport = true;
_delayedTeleport.emplace(newMapid, x, y, z, o);
UnloadStaticPassengers();
return true;
}
@@ -720,20 +648,20 @@ void Transport::DelayedTeleportTransport()
if (!_delayedTeleport)
return;
_delayedTeleport = false;
Map* newMap = sMapMgr->CreateBaseMap(_nextFrame->Node->ContinentID);
Map* newMap = sMapMgr->CreateBaseMap(_delayedTeleport->GetMapId());
GetMap()->RemoveFromMap<Transport>(this, false);
SetMap(newMap);
float x = _nextFrame->Node->Loc.X,
y = _nextFrame->Node->Loc.Y,
z = _nextFrame->Node->Loc.Z,
o =_nextFrame->InitialOrientation;
float x = _delayedTeleport->GetPositionX(),
y = _delayedTeleport->GetPositionY(),
z = _delayedTeleport->GetPositionZ(),
o = _delayedTeleport->GetOrientation();
for (_passengerTeleportItr = _passengers.begin(); _passengerTeleportItr != _passengers.end();)
_delayedTeleport.reset();
PassengerSet passengersToTeleport = _passengers;
for (WorldObject* obj : passengersToTeleport)
{
WorldObject* obj = (*_passengerTeleportItr++);
float destX, destY, destZ, destO;
obj->m_movementInfo.transport.pos.GetPosition(destX, destY, destZ, destO);
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, o);
@@ -741,7 +669,7 @@ void Transport::DelayedTeleportTransport()
switch (obj->GetTypeId())
{
case TYPEID_PLAYER:
if (!obj->ToPlayer()->TeleportTo(_nextFrame->Node->ContinentID, destX, destY, destZ, destO, TELE_TO_NOT_LEAVE_TRANSPORT))
if (!obj->ToPlayer()->TeleportTo(newMap->GetId(), destX, destY, destZ, destO, TELE_TO_NOT_LEAVE_TRANSPORT, newMap->GetInstanceId()))
RemovePassenger(obj);
break;
case TYPEID_DYNAMICOBJECT:
@@ -769,23 +697,15 @@ void Transport::UpdatePassengerPositions(PassengerSet const& passengers)
}
}
void Transport::DoEventIfAny(KeyFrame const& node, bool departure)
{
if (uint32 eventid = departure ? node.Node->DepartureEventID : node.Node->ArrivalEventID)
{
TC_LOG_DEBUG("maps.script", "Taxi %s event %u of node %u of %s path", departure ? "departure" : "arrival", eventid, node.Node->NodeIndex, GetName().c_str());
GameEvents::Trigger(eventid, this, this);
}
}
void Transport::BuildUpdate(UpdateDataMapType& data_map)
{
Map::PlayerList const& players = GetMap()->GetPlayers();
if (players.isEmpty())
return;
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
BuildFieldsUpdate(itr->GetSource(), data_map);
for (MapReference const& playerReference : players)
if (playerReference.GetSource()->IsInPhase(this))
BuildFieldsUpdate(playerReference.GetSource(), data_map);
ClearUpdateMask(true);
}

View File

@@ -21,6 +21,7 @@
#include "GameObject.h"
#include "TransportMgr.h"
#include "VehicleDefines.h"
#include <boost/dynamic_bitset_fwd.hpp>
struct CreatureData;
struct SummonPropertiesEntry;
@@ -90,8 +91,6 @@ class TC_GAME_API Transport : public GameObject, public TransportBase
void SetPeriod(uint32 period) { SetLevel(period); }
uint32 GetTimer() const { return _pathProgress; }
KeyFrameVec const& GetKeyFrames() const { return _transportInfo->keyFrames; }
void UpdatePosition(float x, float y, float z, float o);
//! Needed when transport moves from inactive to active grid
@@ -104,41 +103,26 @@ class TC_GAME_API Transport : public GameObject, public TransportBase
void SetDelayedAddModelToMap() { _delayedAddModel = true; }
TransportTemplate const* GetTransportTemplate() const { return _transportInfo; }
std::string GetDebugInfo() const override;
private:
void MoveToNextWaypoint();
float CalculateSegmentPos(float perc);
bool TeleportTransport(uint32 newMapid, float x, float y, float z, float o);
void DelayedTeleportTransport();
void UpdatePassengerPositions(PassengerSet const& passengers);
void DoEventIfAny(KeyFrame const& node, bool departure);
//! Helpers to know if stop frame was reached
bool IsMoving() const { return _isMoving; }
void SetMoving(bool val) { _isMoving = val; }
TransportTemplate const* _transportInfo;
KeyFrameVec::const_iterator _currentFrame;
KeyFrameVec::const_iterator _nextFrame;
TransportMovementState _movementState;
std::unique_ptr<boost::dynamic_bitset<uint8>> _eventsToTrigger;
size_t _currentPathLeg;
Optional<uint32> _requestStopTimestamp;
uint32 _pathProgress;
TimeTracker _positionChangeTimer;
bool _isMoving;
bool _pendingStop;
//! These are needed to properly control events triggering only once for each frame
bool _triggeredArrivalEvent;
bool _triggeredDepartureEvent;
PassengerSet _passengers;
PassengerSet::iterator _passengerTeleportItr;
PassengerSet _staticPassengers;
bool _delayedAddModel;
bool _delayedTeleport;
Optional<WorldLocation> _delayedTeleport;
};
#endif

View File

@@ -29,12 +29,146 @@
#include "Spline.h"
#include "Transport.h"
bool KeyFrame::IsStopFrame() const
TransportPathLeg::TransportPathLeg() = default;
TransportPathLeg::~TransportPathLeg() = default;
TransportPathLeg::TransportPathLeg(TransportPathLeg&&) noexcept = default;
TransportPathLeg& TransportPathLeg::operator=(TransportPathLeg&&) noexcept = default;
TransportTemplate::TransportTemplate() = default;
TransportTemplate::~TransportTemplate() = default;
TransportTemplate::TransportTemplate(TransportTemplate&&) noexcept = default;
TransportTemplate& TransportTemplate::operator=(TransportTemplate&&) noexcept = default;
Optional<Position> TransportTemplate::ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const
{
return (Node->Flags & TAXI_PATH_NODE_FLAG_STOP) != 0;
time %= TotalPathTime;
// find leg
auto legItr = PathLegs.begin();
while (legItr->StartTimestamp + legItr->Duration <= time)
{
++legItr;
if (legItr == PathLegs.end())
return {};
}
// find segment
uint32 prevSegmentTime = legItr->StartTimestamp;
auto segmentItr = legItr->Segments.begin();
double distanceMoved = 0.0;
bool isOnPause = false;
for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
{
if (time < segmentItr->SegmentEndArrivalTimestamp)
break;
distanceMoved = segmentItr->DistanceFromLegStartAtEnd;
if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
{
isOnPause = true;
break;
}
prevSegmentTime = segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
}
if (!isOnPause)
distanceMoved += CalculateDistanceMoved(
double(time - prevSegmentTime) * 0.001,
double(segmentItr->SegmentEndArrivalTimestamp - prevSegmentTime) * 0.001,
segmentItr == legItr->Segments.begin(),
segmentItr == std::prev(legItr->Segments.end()));
Movement::SplineBase::index_type splineIndex;
float splinePointProgress;
legItr->Spline->computeIndex(std::fmin(distanceMoved / legItr->Spline->length(), 1.0), splineIndex, splinePointProgress);
G3D::Vector3 pos, dir;
legItr->Spline->evaluate_percent(splineIndex, splinePointProgress, pos);
legItr->Spline->evaluate_derivative(splineIndex, splinePointProgress, dir);
if (moveState)
*moveState = isOnPause ? TransportMovementState::WaitingOnPauseWaypoint : TransportMovementState::Moving;
if (legIndex)
*legIndex = std::distance(PathLegs.begin(), legItr);
return Position(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI));
}
TransportTemplate::~TransportTemplate() = default;
double TransportTemplate::CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const
{
if (isFirstSegment)
{
if (!isLastSegment)
{
double accelerationTime = std::fmin(AccelerationTime, segmentDuration);
double segmentTimeAtFullSpeed = segmentDuration - accelerationTime;
if (timePassedInSegment <= segmentTimeAtFullSpeed)
{
return timePassedInSegment * Speed;
}
else
{
double segmentAccelerationTime = timePassedInSegment - segmentTimeAtFullSpeed;
double segmentAccelerationDistance = AccelerationRate * accelerationTime;
double segmentDistanceAtFullSpeed = segmentTimeAtFullSpeed * Speed;
return (2.0 * segmentAccelerationDistance - segmentAccelerationTime * AccelerationRate) * 0.5 * segmentAccelerationTime + segmentDistanceAtFullSpeed;
}
}
return timePassedInSegment * Speed;
}
if (isLastSegment)
{
if (!isFirstSegment)
{
if (timePassedInSegment <= std::fmin(AccelerationTime, segmentDuration))
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
else
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
}
return timePassedInSegment * Speed;
}
double accelerationTime = std::fmin(segmentDuration * 0.5, AccelerationTime);
if (timePassedInSegment <= segmentDuration - accelerationTime)
{
if (timePassedInSegment <= accelerationTime)
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
else
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
}
else
{
double segmentTimeSpentAccelerating = timePassedInSegment - (segmentDuration - accelerationTime);
return (segmentDuration - 2 * accelerationTime) * Speed
+ AccelerationRate * accelerationTime * 0.5 * accelerationTime
+ (2.0 * AccelerationRate * accelerationTime - segmentTimeSpentAccelerating * AccelerationRate) * 0.5 * segmentTimeSpentAccelerating;
}
}
uint32 TransportTemplate::GetNextPauseWaypointTimestamp(uint32 time) const
{
auto legItr = PathLegs.begin();
while (legItr->StartTimestamp + legItr->Duration <= time)
{
++legItr;
if (legItr == PathLegs.end())
return time;
}
auto segmentItr = legItr->Segments.begin();
for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
break;
return segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
}
TransportMgr::TransportMgr() = default;
@@ -87,12 +221,14 @@ void TransportMgr::LoadTransportTemplates()
// paths are generated per template, saves us from generating it again in case of instanced transports
TransportTemplate& transport = _transportTemplates[entry];
transport.entry = entry;
GeneratePath(goInfo, &transport);
std::set<uint32> mapsUsed;
GeneratePath(goInfo, &transport, &mapsUsed);
// transports in instance are only on one map
if (transport.inInstance)
_instanceTransports[*transport.mapsUsed.begin()].insert(entry);
if (transport.InInstance)
_instanceTransports[*mapsUsed.begin()].insert(entry);
++count;
} while (result->NextRow());
@@ -203,239 +339,206 @@ public:
Movement::PointsArray& _points;
};
void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport)
static void InitializeLeg(TransportPathLeg* leg, std::vector<TransportPathEvent>* outEvents, std::vector<TaxiPathNodeEntry const*> const& pathPoints, std::vector<TaxiPathNodeEntry const*> const& pauses,
std::vector<TaxiPathNodeEntry const*> const& events, GameObjectTemplate const* goInfo, uint32& totalTime)
{
Movement::PointsArray splinePath;
std::transform(pathPoints.begin(), pathPoints.end(), std::back_inserter(splinePath), [](TaxiPathNodeEntry const* node) { return Movement::Vector3(node->Loc.X, node->Loc.Y, node->Loc.Z); });
SplineRawInitializer initer(splinePath);
leg->Spline = std::make_unique<TransportSpline>();
leg->Spline->set_steps_per_segment(20);
leg->Spline->init_spline_custom(initer);
leg->Spline->initLengths();
leg->Segments.resize(pauses.size() + 1);
auto legTimeAccelDecel = [&](double dist)
{
double speed = double(goInfo->moTransport.moveSpeed);
double accel = double(goInfo->moTransport.accelRate);
double accelDist = 0.5 * speed * speed / accel;
if (accelDist >= dist * 0.5)
return uint32(std::sqrt(dist / accel) * 2000.0);
else
return uint32((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0);
};
auto legTimeAccel = [&](double dist)
{
double speed = double(goInfo->moTransport.moveSpeed);
double accel = double(goInfo->moTransport.accelRate);
double accelDist = 0.5 * speed * speed / accel;
if (accelDist >= dist)
return uint32(std::sqrt((dist + dist) / accel) * 1000.0);
else
return uint32(((dist - accelDist) / speed + speed / accel) * 1000.0);
};
// Init segments
size_t pauseItr = 0;
size_t eventItr = 0;
double splineLengthToPreviousNode = 0.0;
uint32 delaySum = 0;
if (!pauses.empty())
{
for (; pauseItr < pauses.size(); ++pauseItr)
{
auto pausePointItr = std::find(pathPoints.begin(), pathPoints.end(), pauses[pauseItr]);
if (*pausePointItr == pathPoints.back()) // last point is a "fake" spline point, its position can never be reached so transport cannot stop there
break;
for (; eventItr < events.size(); ++eventItr)
{
auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
if (eventPointItr > pausePointItr)
break;
double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
uint32 splineTime = 0;
if (pauseItr)
splineTime = legTimeAccelDecel(length);
else
splineTime = legTimeAccel(length);
if ((*eventPointItr)->ArrivalEventID)
{
TransportPathEvent& event = outEvents->emplace_back();
event.Timestamp = totalTime + splineTime + leg->Duration;
event.EventId = (*eventPointItr)->ArrivalEventID;
}
if ((*eventPointItr)->DepartureEventID)
{
TransportPathEvent& event = outEvents->emplace_back();
event.Timestamp = totalTime + splineTime + leg->Duration + (pausePointItr == eventPointItr ? (*eventPointItr)->Delay * IN_MILLISECONDS : 0);
event.EventId = (*eventPointItr)->DepartureEventID;
}
}
double splineLengthToCurrentNode = leg->Spline->length(std::distance(pathPoints.begin(), pausePointItr));
double length = splineLengthToCurrentNode - splineLengthToPreviousNode;
uint32 movementTime = 0;
if (pauseItr)
movementTime = legTimeAccelDecel(length);
else
movementTime = legTimeAccel(length);
leg->Duration += movementTime;
leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration + delaySum;
leg->Segments[pauseItr].Delay = (*pausePointItr)->Delay * IN_MILLISECONDS;
leg->Segments[pauseItr].DistanceFromLegStartAtEnd = splineLengthToCurrentNode;
delaySum += (*pausePointItr)->Delay * IN_MILLISECONDS;
splineLengthToPreviousNode = splineLengthToCurrentNode;
}
}
// Process events happening after last pause
for (; eventItr < events.size(); ++eventItr)
{
auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
if (*eventPointItr == pathPoints.back()) // last point is a "fake" spline node, events cannot happen there
break;
double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
uint32 splineTime = 0;
if (pauseItr)
splineTime = legTimeAccel(length);
else
splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
if ((*eventPointItr)->ArrivalEventID)
{
TransportPathEvent& event = outEvents->emplace_back();
event.Timestamp = totalTime + splineTime + leg->Duration;
event.EventId = (*eventPointItr)->ArrivalEventID;
}
if ((*eventPointItr)->DepartureEventID)
{
TransportPathEvent& event = outEvents->emplace_back();
event.Timestamp = totalTime + splineTime + leg->Duration;
event.EventId = (*eventPointItr)->DepartureEventID;
}
}
// Add segment after last pause
double length = leg->Spline->length() - splineLengthToPreviousNode;
uint32 splineTime = 0;
if (pauseItr)
splineTime = legTimeAccel(length);
else
splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
leg->StartTimestamp = totalTime;
leg->Duration += splineTime + delaySum;
leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration;
leg->Segments[pauseItr].Delay = 0;
leg->Segments[pauseItr].DistanceFromLegStartAtEnd = leg->Spline->length();
totalTime += leg->Segments[pauseItr].SegmentEndArrivalTimestamp + leg->Segments[pauseItr].Delay;
for (TransportPathSegment& segment : leg->Segments)
segment.SegmentEndArrivalTimestamp += leg->StartTimestamp;
if (leg->Segments.size() > pauseItr + 1)
leg->Segments.resize(pauseItr + 1);
}
void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed)
{
uint32 pathId = goInfo->moTransport.taxiPathID;
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
std::vector<KeyFrame>& keyFrames = transport->keyFrames;
Movement::PointsArray splinePath, allPoints;
bool mapChange = false;
for (size_t i = 0; i < path.size(); ++i)
allPoints.push_back(G3D::Vector3(path[i]->Loc.X, path[i]->Loc.Y, path[i]->Loc.Z));
// Add extra points to allow derivative calculations for all path nodes
allPoints.insert(allPoints.begin(), allPoints.front().lerp(allPoints[1], -0.2f));
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -0.2f));
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -1.0f));
transport->Speed = double(goInfo->moTransport.moveSpeed);
transport->AccelerationRate = double(goInfo->moTransport.accelRate);
transport->AccelerationTime = transport->Speed / transport->AccelerationRate;
transport->AccelerationDistance = 0.5 * transport->Speed * transport->Speed / transport->AccelerationRate;
SplineRawInitializer initer(allPoints);
TransportSpline orientationSpline;
orientationSpline.init_spline_custom(initer);
orientationSpline.initLengths();
for (size_t i = 0; i < path.size(); ++i)
std::vector<TaxiPathNodeEntry const*> pathPoints;
std::vector<TaxiPathNodeEntry const*> pauses;
std::vector<TaxiPathNodeEntry const*> events;
TransportPathLeg* leg = &transport->PathLegs.emplace_back();
leg->MapId = path[0]->ContinentID;
bool prevNodeWasTeleport = false;
uint32 totalTime = 0;
for (TaxiPathNodeEntry const* node : path)
{
if (!mapChange)
if (node->ContinentID != leg->MapId || prevNodeWasTeleport)
{
TaxiPathNodeEntry const* node_i = path[i];
if (i != path.size() - 1 && (node_i->Flags & TAXI_PATH_NODE_FLAG_TELEPORT || node_i->ContinentID != path[i + 1]->ContinentID))
{
keyFrames.back().Teleport = true;
mapChange = true;
}
else
{
KeyFrame k(node_i);
G3D::Vector3 h;
orientationSpline.evaluate_derivative(i + 1, 0.0f, h);
k.InitialOrientation = Position::NormalizeOrientation(std::atan2(h.y, h.x) + float(M_PI));
InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
keyFrames.push_back(k);
splinePath.push_back(G3D::Vector3(node_i->Loc.X, node_i->Loc.Y, node_i->Loc.Z));
transport->mapsUsed.insert(k.Node->ContinentID);
}
leg = &transport->PathLegs.emplace_back();
leg->MapId = node->ContinentID;
pathPoints.clear();
pauses.clear();
events.clear();
}
else
mapChange = false;
prevNodeWasTeleport = (node->Flags & TAXI_PATH_NODE_FLAG_TELEPORT) != 0;
pathPoints.push_back(node);
if (node->Flags & TAXI_PATH_NODE_FLAG_STOP)
pauses.push_back(node);
if (node->ArrivalEventID || node->DepartureEventID)
events.push_back(node);
mapsUsed->insert(node->ContinentID);
}
if (splinePath.size() >= 2)
if (!leg->Spline)
InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
if (mapsUsed->size() > 1)
{
// Remove special catmull-rom spline points
if (!keyFrames.front().IsStopFrame() && !keyFrames.front().Node->ArrivalEventID && !keyFrames.front().Node->DepartureEventID)
{
splinePath.erase(splinePath.begin());
keyFrames.erase(keyFrames.begin());
}
if (!keyFrames.back().IsStopFrame() && !keyFrames.back().Node->ArrivalEventID && !keyFrames.back().Node->DepartureEventID)
{
splinePath.pop_back();
keyFrames.pop_back();
}
}
for (uint32 mapId : *mapsUsed)
ASSERT(!sMapStore.LookupEntry(mapId)->Instanceable());
ASSERT(!keyFrames.empty());
if (transport->mapsUsed.size() > 1)
{
for (std::set<uint32>::const_iterator itr = transport->mapsUsed.begin(); itr != transport->mapsUsed.end(); ++itr)
ASSERT(!sMapStore.LookupEntry(*itr)->Instanceable());
transport->inInstance = false;
transport->InInstance = false;
}
else
transport->inInstance = sMapStore.LookupEntry(*transport->mapsUsed.begin())->Instanceable();
transport->InInstance = sMapStore.LookupEntry(*mapsUsed->begin())->Instanceable();
// last to first is always "teleport", even for closed paths
keyFrames.back().Teleport = true;
const float speed = float(goInfo->moTransport.moveSpeed);
const float accel = float(goInfo->moTransport.accelRate);
const float accel_dist = 0.5f * speed * speed / accel;
transport->accelTime = speed / accel;
transport->accelDist = accel_dist;
int32 firstStop = -1;
int32 lastStop = -1;
// first cell is arrived at by teleportation :S
keyFrames[0].DistFromPrev = 0;
keyFrames[0].Index = 1;
if (keyFrames[0].IsStopFrame())
{
firstStop = 0;
lastStop = 0;
}
// find the rest of the distances between key points
// Every path segment has its own spline
size_t start = 0;
for (size_t i = 1; i < keyFrames.size(); ++i)
{
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.size())
{
size_t extra = !keyFrames[i - 1].Teleport ? 1 : 0;
std::shared_ptr<TransportSpline> spline = std::make_shared<TransportSpline>();
spline->init_spline(&splinePath[start], i - start + extra, Movement::SplineBase::ModeCatmullrom);
spline->initLengths();
for (size_t j = start; j < i + extra; ++j)
{
keyFrames[j].Index = j - start + 1;
keyFrames[j].DistFromPrev = float(spline->length(j - start, j + 1 - start));
if (j > 0)
keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
keyFrames[j].Spline = spline;
}
if (keyFrames[i - 1].Teleport)
{
keyFrames[i].Index = i - start + 1;
keyFrames[i].DistFromPrev = 0.0f;
keyFrames[i - 1].NextDistFromPrev = 0.0f;
keyFrames[i].Spline = spline;
}
start = i;
}
if (keyFrames[i].IsStopFrame())
{
// remember first stop frame
if (firstStop == -1)
firstStop = i;
lastStop = i;
}
}
keyFrames.back().NextDistFromPrev = keyFrames.front().DistFromPrev;
if (firstStop == -1 || lastStop == -1)
firstStop = lastStop = 0;
// at stopping keyframes, we define distSinceStop == 0,
// and distUntilStop is to the next stopping keyframe.
// this is required to properly handle cases of two stopping frames in a row (yes they do exist)
float tmpDist = 0.0f;
for (size_t i = 0; i < keyFrames.size(); ++i)
{
int32 j = (i + lastStop) % keyFrames.size();
if (keyFrames[j].IsStopFrame() || j == lastStop)
tmpDist = 0.0f;
else
tmpDist += keyFrames[j].DistFromPrev;
keyFrames[j].DistSinceStop = tmpDist;
}
tmpDist = 0.0f;
for (int32 i = int32(keyFrames.size()) - 1; i >= 0; i--)
{
int32 j = (i + firstStop) % keyFrames.size();
tmpDist += keyFrames[(j + 1) % keyFrames.size()].DistFromPrev;
keyFrames[j].DistUntilStop = tmpDist;
if (keyFrames[j].IsStopFrame() || j == firstStop)
tmpDist = 0.0f;
}
for (size_t i = 0; i < keyFrames.size(); ++i)
{
float total_dist = keyFrames[i].DistSinceStop + keyFrames[i].DistUntilStop;
if (total_dist < 2 * accel_dist) // won't reach full speed
{
if (keyFrames[i].DistSinceStop < keyFrames[i].DistUntilStop) // is still accelerating
{
// calculate accel+brake time for this short segment
float segment_time = 2.0f * std::sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel);
// substract acceleration time
keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
}
else // slowing down
keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
}
else if (keyFrames[i].DistSinceStop < accel_dist) // still accelerating (but will reach full speed)
{
// calculate accel + cruise + brake time for this long segment
float segment_time = (keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / speed + (speed / accel);
// substract acceleration time
keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
}
else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed)
keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
else // at full speed
keyFrames[i].TimeTo = (keyFrames[i].DistUntilStop / speed) + (0.5f * speed / accel);
}
// calculate tFrom times from tTo times
float segmentTime = 0.0f;
for (size_t i = 0; i < keyFrames.size(); ++i)
{
int32 j = (i + lastStop) % keyFrames.size();
if (keyFrames[j].IsStopFrame() || j == lastStop)
segmentTime = keyFrames[j].TimeTo;
keyFrames[j].TimeFrom = segmentTime - keyFrames[j].TimeTo;
}
// calculate path times
keyFrames[0].ArriveTime = 0;
float curPathTime = 0.0f;
if (keyFrames[0].IsStopFrame())
{
curPathTime = float(keyFrames[0].Node->Delay);
keyFrames[0].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
}
for (size_t i = 1; i < keyFrames.size(); ++i)
{
curPathTime += keyFrames[i - 1].TimeTo;
if (keyFrames[i].IsStopFrame())
{
keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
curPathTime += float(keyFrames[i].Node->Delay);
keyFrames[i].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
}
else
{
curPathTime -= keyFrames[i].TimeTo;
keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
keyFrames[i].DepartureTime = keyFrames[i].ArriveTime;
}
}
keyFrames.back().NextArriveTime = keyFrames.back().DepartureTime;
transport->pathTime = keyFrames.back().DepartureTime;
transport->TotalPathTime = totalTime;
}
void TransportMgr::AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node)
@@ -477,16 +580,22 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
return nullptr;
}
Optional<Position> startingPosition = tInfo->ComputePosition(0, nullptr, nullptr);
if (!startingPosition)
{
TC_LOG_ERROR("sql.sql", "Transport %u will not be loaded, failed to compute starting position", entry);
return nullptr;
}
// create transport...
Transport* trans = new Transport();
// ...at first waypoint
TaxiPathNodeEntry const* startNode = tInfo->keyFrames.begin()->Node;
uint32 mapId = startNode->ContinentID;
float x = startNode->Loc.X;
float y = startNode->Loc.Y;
float z = startNode->Loc.Z;
float o = tInfo->keyFrames.begin()->InitialOrientation;
uint32 mapId = tInfo->PathLegs.front().MapId;
float x = startingPosition->GetPositionX();
float y = startingPosition->GetPositionY();
float z = startingPosition->GetPositionZ();
float o = startingPosition->GetOrientation();
// initialize the gameobject base
ObjectGuid::LowType guidLow = guid ? guid : ASSERT_NOTNULL(map)->GenerateLowGuid<HighGuid::Transport>();
@@ -500,7 +609,7 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
if (MapEntry const* mapEntry = sMapStore.LookupEntry(mapId))
{
if (mapEntry->Instanceable() != tInfo->inInstance)
if (mapEntry->Instanceable() != tInfo->InInstance)
{
TC_LOG_ERROR("entities.transport", "Transport %u (name: %s) attempted creation in instance map (id: %u) but it is not an instanced transport!", entry, trans->GetName().c_str(), mapId);
delete trans;
@@ -525,7 +634,7 @@ void TransportMgr::SpawnContinentTransports()
uint32 count = 0;
for (auto itr = _transportSpawns.begin(); itr != _transportSpawns.end(); ++itr)
if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->inInstance)
if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->InInstance)
if (CreateTransport(itr->second.TransportGameObjectId, itr->second.SpawnId, nullptr, itr->second.PhaseUseFlags, itr->second.PhaseId, itr->second.PhaseGroup))
++count;

View File

@@ -19,11 +19,12 @@
#define TRANSPORTMGR_H
#include "ObjectGuid.h"
#include "Optional.h"
#include "Position.h"
#include <map>
#include <memory>
#include <unordered_map>
struct KeyFrame;
struct GameObjectTemplate;
struct TaxiPathNodeEntry;
struct TransportAnimationEntry;
@@ -38,49 +39,69 @@ namespace Movement
}
using TransportSpline = Movement::Spline<double>;
using KeyFrameVec = std::vector<KeyFrame>;
struct KeyFrame
enum class TransportMovementState : uint8
{
explicit KeyFrame(TaxiPathNodeEntry const* node) : Index(0), Node(node), InitialOrientation(0.0f),
DistSinceStop(-1.0f), DistUntilStop(-1.0f), DistFromPrev(-1.0f), TimeFrom(0.0f), TimeTo(0.0f),
Teleport(false), ArriveTime(0), DepartureTime(0), Spline(nullptr), NextDistFromPrev(0.0f), NextArriveTime(0)
{
}
Moving,
WaitingOnPauseWaypoint
};
uint32 Index;
TaxiPathNodeEntry const* Node;
float InitialOrientation;
float DistSinceStop;
float DistUntilStop;
float DistFromPrev;
float TimeFrom;
float TimeTo;
bool Teleport;
uint32 ArriveTime;
uint32 DepartureTime;
std::shared_ptr<TransportSpline> Spline;
// Represents a segment within path leg between stops
struct TransportPathSegment
{
uint32 SegmentEndArrivalTimestamp = 0;
uint32 Delay = 0;
double DistanceFromLegStartAtEnd = 0.0;
};
// Data needed for next frame
float NextDistFromPrev;
uint32 NextArriveTime;
struct TransportPathEvent
{
uint32 Timestamp = 0;
uint32 EventId = 0;
};
bool IsTeleportFrame() const { return Teleport; }
bool IsStopFrame() const;
// Represents a contignuous part of transport path (without map changes or teleports)
struct TransportPathLeg
{
TransportPathLeg();
~TransportPathLeg();
TransportPathLeg(TransportPathLeg const&) = delete;
TransportPathLeg(TransportPathLeg&&) noexcept;
TransportPathLeg& operator=(TransportPathLeg const&) = delete;
TransportPathLeg& operator=(TransportPathLeg&&) noexcept;
uint32 MapId = 0;
std::unique_ptr<TransportSpline> Spline;
uint32 StartTimestamp = 0;
uint32 Duration = 0;
std::vector<TransportPathSegment> Segments;
};
struct TransportTemplate
{
TransportTemplate() : inInstance(false), pathTime(0), accelTime(0.0f), accelDist(0.0f), entry(0) { }
TransportTemplate();
~TransportTemplate();
std::set<uint32> mapsUsed;
bool inInstance;
uint32 pathTime;
KeyFrameVec keyFrames;
float accelTime;
float accelDist;
uint32 entry;
TransportTemplate(TransportTemplate const&) = delete;
TransportTemplate(TransportTemplate&&) noexcept;
TransportTemplate& operator=(TransportTemplate const&) = delete;
TransportTemplate& operator=(TransportTemplate&&) noexcept;
uint32 TotalPathTime = 0;
double Speed = 0.0;
double AccelerationRate = 0.0;
double AccelerationTime = 0.0;
double AccelerationDistance = 0.0;
std::vector<TransportPathLeg> PathLegs;
std::vector<TransportPathEvent> Events;
Optional<Position> ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const;
uint32 GetNextPauseWaypointTimestamp(uint32 time) const;
double CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const;
bool InInstance = false;
};
struct TC_GAME_API TransportAnimation
@@ -142,7 +163,7 @@ class TC_GAME_API TransportMgr
TransportMgr& operator=(TransportMgr const&) = delete;
// Generates and precaches a path for transport to avoid generation each time transport instance is created
void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport);
void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed);
void AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node);

View File

@@ -1921,10 +1921,10 @@ void ScriptMgr::OnTransportUpdate(Transport* transport, uint32 diff)
tmpscript->OnUpdate(transport, diff);
}
void ScriptMgr::OnRelocate(Transport* transport, uint32 waypointId, uint32 mapId, float x, float y, float z)
void ScriptMgr::OnRelocate(Transport* transport, uint32 mapId, float x, float y, float z)
{
GET_SCRIPT(TransportScript, transport->GetScriptId(), tmpscript);
tmpscript->OnRelocate(transport, waypointId, mapId, x, y, z);
tmpscript->OnRelocate(transport, mapId, x, y, z);
}
void ScriptMgr::OnStartup()

View File

@@ -677,7 +677,7 @@ class TC_GAME_API TransportScript : public ScriptObject, public UpdatableScript<
virtual void OnRemovePassenger(Transport* /*transport*/, Player* /*player*/) { }
// Called when a transport moves.
virtual void OnRelocate(Transport* /*transport*/, uint32 /*waypointId*/, uint32 /*mapId*/, float /*x*/, float /*y*/, float /*z*/) { }
virtual void OnRelocate(Transport* /*transport*/, uint32 /*mapId*/, float /*x*/, float /*y*/, float /*z*/) { }
};
class TC_GAME_API AchievementScript : public ScriptObject
@@ -1180,7 +1180,7 @@ class TC_GAME_API ScriptMgr
void OnAddCreaturePassenger(Transport* transport, Creature* creature);
void OnRemovePassenger(Transport* transport, Player* player);
void OnTransportUpdate(Transport* transport, uint32 diff);
void OnRelocate(Transport* transport, uint32 waypointId, uint32 mapId, float x, float y, float z);
void OnRelocate(Transport* transport, uint32 mapId, float x, float y, float z);
public: /* AchievementScript */