diff options
author | Shauren <shauren.trinity@gmail.com> | 2022-06-07 16:02:51 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-06-07 16:02:51 +0200 |
commit | a2c1b699e6e4d30c752b65241bc23191920a86fd (patch) | |
tree | 7e580185397acd299326a714da71406f15c30ddc /src | |
parent | b9353041a6cce58d972b63d5138517274a1bb6d6 (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.h | 14 | ||||
-rw-r--r-- | src/server/game/Entities/Transport/Transport.cpp | 276 | ||||
-rw-r--r-- | src/server/game/Entities/Transport/Transport.h | 28 | ||||
-rw-r--r-- | src/server/game/Maps/TransportMgr.cpp | 533 | ||||
-rw-r--r-- | src/server/game/Maps/TransportMgr.h | 95 | ||||
-rw-r--r-- | src/server/game/Scripting/ScriptMgr.cpp | 4 | ||||
-rw-r--r-- | src/server/game/Scripting/ScriptMgr.h | 4 |
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 */ |