aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2022-06-07 16:02:51 +0200
committerShauren <shauren.trinity@gmail.com>2022-06-07 16:02:51 +0200
commita2c1b699e6e4d30c752b65241bc23191920a86fd (patch)
tree7e580185397acd299326a714da71406f15c30ddc /src
parentb9353041a6cce58d972b63d5138517274a1bb6d6 (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Entities/Object/Updates/ViewerDependentValues.h14
-rw-r--r--src/server/game/Entities/Transport/Transport.cpp276
-rw-r--r--src/server/game/Entities/Transport/Transport.h28
-rw-r--r--src/server/game/Maps/TransportMgr.cpp533
-rw-r--r--src/server/game/Maps/TransportMgr.h95
-rw-r--r--src/server/game/Scripting/ScriptMgr.cpp4
-rw-r--r--src/server/game/Scripting/ScriptMgr.h4
7 files changed, 489 insertions, 465 deletions
diff --git a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
index 755006262bd..564b31a9b86 100644
--- a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
+++ b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
@@ -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;
diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp
index 926ead58655..906fd3793a0 100644
--- a/src/server/game/Entities/Transport/Transport.cpp
+++ b/src/server/game/Entities/Transport/Transport.cpp
@@ -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);
+
+ _positionChangeTimer.Update(diff);
- if (IsMoving() || !_pendingStop)
+ 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;
+ }
+ }
+
+ 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 (timer < _currentFrame->DepartureTime)
+ 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);
-
- // 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
+ // 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());
+
+ 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();)
- {
- WorldObject* obj = (*_passengerTeleportItr++);
+ _delayedTeleport.reset();
+ PassengerSet passengersToTeleport = _passengers;
+ for (WorldObject* obj : passengersToTeleport)
+ {
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);
}
diff --git a/src/server/game/Entities/Transport/Transport.h b/src/server/game/Entities/Transport/Transport.h
index 2166fade720..f5e5a30e369 100644
--- a/src/server/game/Entities/Transport/Transport.h
+++ b/src/server/game/Entities/Transport/Transport.h
@@ -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
diff --git a/src/server/game/Maps/TransportMgr.cpp b/src/server/game/Maps/TransportMgr.cpp
index 5bcd0db73b0..3c8fcc3498a 100644
--- a/src/server/game/Maps/TransportMgr.cpp
+++ b/src/server/game/Maps/TransportMgr.cpp
@@ -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)
{
- 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));
-
- SplineRawInitializer initer(allPoints);
- TransportSpline orientationSpline;
- orientationSpline.init_spline_custom(initer);
- orientationSpline.initLengths();
-
- for (size_t i = 0; i < path.size(); ++i)
+ 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)
{
- if (!mapChange)
- {
- 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));
-
- 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);
- }
- }
+ 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
- mapChange = false;
- }
+ return uint32((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0);
+ };
- if (splinePath.size() >= 2)
+ auto legTimeAccel = [&](double dist)
{
- // 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();
- }
- }
-
- ASSERT(!keyFrames.empty());
-
- if (transport->mapsUsed.size() > 1)
+ 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 (std::set<uint32>::const_iterator itr = transport->mapsUsed.begin(); itr != transport->mapsUsed.end(); ++itr)
- ASSERT(!sMapStore.LookupEntry(*itr)->Instanceable());
+ 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;
- transport->inInstance = false;
- }
- else
- transport->inInstance = sMapStore.LookupEntry(*transport->mapsUsed.begin())->Instanceable();
+ for (; eventItr < events.size(); ++eventItr)
+ {
+ auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
+ if (eventPointItr > pausePointItr)
+ break;
- // last to first is always "teleport", even for closed paths
- keyFrames.back().Teleport = true;
+ double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
- const float speed = float(goInfo->moTransport.moveSpeed);
- const float accel = float(goInfo->moTransport.accelRate);
- const float accel_dist = 0.5f * speed * speed / accel;
+ uint32 splineTime = 0;
+ if (pauseItr)
+ splineTime = legTimeAccelDecel(length);
+ else
+ splineTime = legTimeAccel(length);
- transport->accelTime = speed / accel;
- transport->accelDist = accel_dist;
+ if ((*eventPointItr)->ArrivalEventID)
+ {
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->ArrivalEventID;
+ }
- int32 firstStop = -1;
- int32 lastStop = -1;
+ 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;
+ }
+ }
- // first cell is arrived at by teleportation :S
- keyFrames[0].DistFromPrev = 0;
- keyFrames[0].Index = 1;
- if (keyFrames[0].IsStopFrame())
- {
- firstStop = 0;
- lastStop = 0;
+ 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;
+ }
}
- // 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)
+ // Process events happening after last pause
+ for (; eventItr < events.size(); ++eventItr)
{
- 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;
- }
+ 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);
- start = i;
+ if ((*eventPointItr)->ArrivalEventID)
+ {
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->ArrivalEventID;
}
- if (keyFrames[i].IsStopFrame())
+ if ((*eventPointItr)->DepartureEventID)
{
- // remember first stop frame
- if (firstStop == -1)
- firstStop = i;
- lastStop = i;
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->DepartureEventID;
}
}
- keyFrames.back().NextDistFromPrev = keyFrames.front().DistFromPrev;
+ // 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);
- if (firstStop == -1 || lastStop == -1)
- firstStop = lastStop = 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;
- // 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;
- }
+ for (TransportPathSegment& segment : leg->Segments)
+ segment.SegmentEndArrivalTimestamp += leg->StartTimestamp;
- 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;
- }
+ 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];
- for (size_t i = 0; i < keyFrames.size(); ++i)
+ 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;
+
+ 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)
{
- 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)
+ if (node->ContinentID != leg->MapId || prevNodeWasTeleport)
{
- // 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);
+ InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
+
+ leg = &transport->PathLegs.emplace_back();
+ leg->MapId = node->ContinentID;
+ pathPoints.clear();
+ pauses.clear();
+ events.clear();
}
- 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;
- }
+ 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);
- // 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);
+ if (node->ArrivalEventID || node->DepartureEventID)
+ events.push_back(node);
+
+ mapsUsed->insert(node->ContinentID);
}
- for (size_t i = 1; i < keyFrames.size(); ++i)
+ if (!leg->Spline)
+ InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
+
+ if (mapsUsed->size() > 1)
{
- 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;
- }
- }
+ for (uint32 mapId : *mapsUsed)
+ ASSERT(!sMapStore.LookupEntry(mapId)->Instanceable());
- keyFrames.back().NextArriveTime = keyFrames.back().DepartureTime;
+ transport->InInstance = false;
+ }
+ else
+ transport->InInstance = sMapStore.LookupEntry(*mapsUsed->begin())->Instanceable();
- 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;
diff --git a/src/server/game/Maps/TransportMgr.h b/src/server/game/Maps/TransportMgr.h
index 92ce809faee..66f6d59e35c 100644
--- a/src/server/game/Maps/TransportMgr.h
+++ b/src/server/game/Maps/TransportMgr.h
@@ -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)
- {
- }
-
- 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;
-
- // Data needed for next frame
- float NextDistFromPrev;
- uint32 NextArriveTime;
-
- bool IsTeleportFrame() const { return Teleport; }
- bool IsStopFrame() const;
+ Moving,
+ WaitingOnPauseWaypoint
+};
+
+// Represents a segment within path leg between stops
+struct TransportPathSegment
+{
+ uint32 SegmentEndArrivalTimestamp = 0;
+ uint32 Delay = 0;
+ double DistanceFromLegStartAtEnd = 0.0;
+};
+
+struct TransportPathEvent
+{
+ uint32 Timestamp = 0;
+ uint32 EventId = 0;
+};
+
+// 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);
diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp
index 35aaf0829cb..795b8e997eb 100644
--- a/src/server/game/Scripting/ScriptMgr.cpp
+++ b/src/server/game/Scripting/ScriptMgr.cpp
@@ -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()
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index bca92bbe3af..32ecfb431c7 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -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 */