mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Core/Transports: Path generation rewrite v2
* No TaxiPathNode row is skipped anymore (events/delays on first node now work) * Fixed transport animation synchronization with client * Fixed stoppable transports randomly resuming their path after relogging Closes #27985
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include "Cell.h"
|
||||
#include "CellImpl.h"
|
||||
#include "Common.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "GameEventSender.h"
|
||||
#include "GameObjectAI.h"
|
||||
#include "GameTime.h"
|
||||
#include "Log.h"
|
||||
#include "MapManager.h"
|
||||
#include "ObjectMgr.h"
|
||||
@@ -31,6 +33,7 @@
|
||||
#include "Totem.h"
|
||||
#include "UpdateData.h"
|
||||
#include "Vehicle.h"
|
||||
#include <boost/dynamic_bitset.hpp>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <sstream>
|
||||
|
||||
@@ -90,9 +93,8 @@ void TransportBase::UpdatePassengerPosition(Map* map, WorldObject* passenger, fl
|
||||
}
|
||||
|
||||
Transport::Transport() : GameObject(),
|
||||
_transportInfo(nullptr), _pathProgress(0), _isMoving(true), _pendingStop(false),
|
||||
_triggeredArrivalEvent(false), _triggeredDepartureEvent(false),
|
||||
_passengerTeleportItr(_passengers.begin()), _delayedAddModel(false), _delayedTeleport(false)
|
||||
_transportInfo(nullptr), _movementState(TransportMovementState::Moving), _eventsToTrigger(std::make_unique<boost::dynamic_bitset<uint8>>()),
|
||||
_currentPathLeg(0), _pathProgress(0), _delayedAddModel(false)
|
||||
{
|
||||
m_updateFlag.ServerTime = true;
|
||||
m_updateFlag.Stationary = true;
|
||||
@@ -136,12 +138,7 @@ bool Transport::Create(ObjectGuid::LowType guidlow, uint32 entry, uint32 mapid,
|
||||
}
|
||||
|
||||
_transportInfo = tInfo;
|
||||
|
||||
// initialize waypoints
|
||||
_nextFrame = tInfo->keyFrames.begin();
|
||||
_currentFrame = _nextFrame++;
|
||||
_triggeredArrivalEvent = false;
|
||||
_triggeredDepartureEvent = false;
|
||||
_eventsToTrigger->resize(tInfo->Events.size(), true);
|
||||
|
||||
if (GameObjectOverride const* goOverride = GetGameObjectOverride())
|
||||
{
|
||||
@@ -149,14 +146,16 @@ bool Transport::Create(ObjectGuid::LowType guidlow, uint32 entry, uint32 mapid,
|
||||
ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
|
||||
}
|
||||
|
||||
_pathProgress = 0;
|
||||
_pathProgress = !goinfo->moTransport.allowstopping ? getMSTime() /*might be called before world update loop begins, don't use GameTime*/ % tInfo->TotalPathTime : 0;
|
||||
SetPathProgressForClient(float(_pathProgress) / float(tInfo->TotalPathTime));
|
||||
SetObjectScale(goinfo->size);
|
||||
SetPeriod(tInfo->pathTime);
|
||||
SetPeriod(tInfo->TotalPathTime);
|
||||
SetEntry(goinfo->entry);
|
||||
SetDisplayId(goinfo->displayId);
|
||||
SetGoState(!goinfo->moTransport.allowstopping ? GO_STATE_READY : GO_STATE_ACTIVE);
|
||||
SetGoType(GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT);
|
||||
SetGoAnimProgress(animprogress);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpawnTrackingStateAnimID), sDB2Manager.GetEmptyAnimStateID());
|
||||
SetName(goinfo->name);
|
||||
SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
SetParentRotation(QuaternionData());
|
||||
@@ -179,77 +178,93 @@ void Transport::CleanupsBeforeDelete(bool finalCleanup /*= true*/)
|
||||
|
||||
void Transport::Update(uint32 diff)
|
||||
{
|
||||
uint32 const positionUpdateDelay = 200;
|
||||
constexpr Milliseconds positionUpdateDelay = 200ms;
|
||||
|
||||
if (AI())
|
||||
AI()->UpdateAI(diff);
|
||||
else if (!AIM_Initialize())
|
||||
TC_LOG_ERROR("entities.transport", "Could not initialize GameObjectAI for Transport");
|
||||
|
||||
if (GetKeyFrames().size() <= 1)
|
||||
return;
|
||||
sScriptMgr->OnTransportUpdate(this, diff);
|
||||
|
||||
if (IsMoving() || !_pendingStop)
|
||||
_positionChangeTimer.Update(diff);
|
||||
|
||||
uint32 cycleId = _pathProgress / GetTransportPeriod();
|
||||
if (!GetGOInfo()->moTransport.allowstopping)
|
||||
_pathProgress = GameTime::GetGameTimeMS();
|
||||
else if (!_requestStopTimestamp || _requestStopTimestamp > _pathProgress + diff)
|
||||
_pathProgress += diff;
|
||||
else
|
||||
_pathProgress = *_requestStopTimestamp;
|
||||
|
||||
if (_pathProgress / GetTransportPeriod() != cycleId)
|
||||
{
|
||||
// reset cycle
|
||||
_eventsToTrigger->set();
|
||||
}
|
||||
|
||||
SetPathProgressForClient(float(_pathProgress) / float(GetTransportPeriod()));
|
||||
|
||||
uint32 timer = _pathProgress % GetTransportPeriod();
|
||||
bool justStopped = false;
|
||||
|
||||
// Set current waypoint
|
||||
// Desired outcome: _currentFrame->DepartureTime < timer < _nextFrame->ArriveTime
|
||||
// ... arrive | ... delay ... | departure
|
||||
// event / event /
|
||||
for (;;)
|
||||
size_t eventToTriggerIndex = _eventsToTrigger->find_first();
|
||||
if (eventToTriggerIndex != boost::dynamic_bitset<uint8>::npos)
|
||||
{
|
||||
if (timer >= _currentFrame->ArriveTime)
|
||||
while (eventToTriggerIndex < _transportInfo->Events.size() && _transportInfo->Events[eventToTriggerIndex].Timestamp < timer)
|
||||
{
|
||||
if (!_triggeredArrivalEvent)
|
||||
{
|
||||
DoEventIfAny(*_currentFrame, false);
|
||||
_triggeredArrivalEvent = true;
|
||||
}
|
||||
GameEvents::Trigger(_transportInfo->Events[eventToTriggerIndex].EventId, this, this);
|
||||
_eventsToTrigger->set(eventToTriggerIndex, false);
|
||||
++eventToTriggerIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (timer < _currentFrame->DepartureTime)
|
||||
TransportMovementState moveState;
|
||||
size_t legIndex;
|
||||
if (Optional<Position> newPosition = _transportInfo->ComputePosition(timer, &moveState, &legIndex))
|
||||
{
|
||||
bool justStopped = _movementState == TransportMovementState::Moving && moveState != TransportMovementState::Moving;
|
||||
_movementState = moveState;
|
||||
|
||||
if (justStopped)
|
||||
{
|
||||
if (_requestStopTimestamp && GetGoState() != GO_STATE_READY)
|
||||
{
|
||||
justStopped = IsMoving();
|
||||
SetMoving(false);
|
||||
if (_pendingStop && GetGoState() != GO_STATE_READY)
|
||||
{
|
||||
SetGoState(GO_STATE_READY);
|
||||
_pathProgress = (_pathProgress / GetTransportPeriod());
|
||||
_pathProgress *= GetTransportPeriod();
|
||||
_pathProgress += _currentFrame->ArriveTime;
|
||||
}
|
||||
break; // its a stop frame and we are waiting
|
||||
SetGoState(GO_STATE_READY);
|
||||
SetDynamicFlag(GO_DYNFLAG_LO_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
if (timer >= _currentFrame->DepartureTime && !_triggeredDepartureEvent)
|
||||
if (legIndex != _currentPathLeg)
|
||||
{
|
||||
DoEventIfAny(*_currentFrame, true); // departure event
|
||||
_triggeredDepartureEvent = true;
|
||||
_currentPathLeg = legIndex;
|
||||
TeleportTransport(_transportInfo->PathLegs[legIndex].MapId, newPosition->GetPositionX(), newPosition->GetPositionY(), newPosition->GetPositionZ(), newPosition->GetOrientation());
|
||||
return;
|
||||
}
|
||||
|
||||
// not waiting anymore
|
||||
SetMoving(true);
|
||||
// set position
|
||||
if (_positionChangeTimer.Passed())
|
||||
{
|
||||
_positionChangeTimer.Reset(positionUpdateDelay);
|
||||
if (_movementState == TransportMovementState::Moving || justStopped)
|
||||
UpdatePosition(newPosition->GetPositionX(), newPosition->GetPositionY(), newPosition->GetPositionZ(), newPosition->GetOrientation());
|
||||
else
|
||||
{
|
||||
/* There are four possible scenarios that trigger loading/unloading passengers:
|
||||
1. transport moves from inactive to active grid
|
||||
2. the grid that transport is currently in becomes active
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
bool gridActive = GetMap()->IsGridLoaded(GetPositionX(), GetPositionY());
|
||||
|
||||
// Enable movement
|
||||
if (GetGOInfo()->moTransport.allowstopping)
|
||||
SetGoState(GO_STATE_ACTIVE);
|
||||
|
||||
if (timer >= _currentFrame->DepartureTime && timer < _currentFrame->NextArriveTime)
|
||||
break; // found current waypoint
|
||||
|
||||
MoveToNextWaypoint();
|
||||
|
||||
sScriptMgr->OnRelocate(this, _currentFrame->Node->NodeIndex, _currentFrame->Node->ContinentID, _currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z);
|
||||
|
||||
TC_LOG_DEBUG("entities.transport", "Transport %u (%s) moved to node %u %u %f %f %f", GetEntry(), GetName().c_str(), _currentFrame->Node->NodeIndex, _currentFrame->Node->ContinentID, _currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z);
|
||||
|
||||
// Departure event
|
||||
if (_currentFrame->IsTeleportFrame())
|
||||
if (TeleportTransport(_nextFrame->Node->ContinentID, _nextFrame->Node->Loc.X, _nextFrame->Node->Loc.Y, _nextFrame->Node->Loc.Z, _nextFrame->InitialOrientation))
|
||||
return; // Update more in new map thread
|
||||
if (_staticPassengers.empty() && gridActive) // 2.
|
||||
LoadStaticPassengers();
|
||||
else if (!_staticPassengers.empty() && !gridActive)
|
||||
// 4. - if transports stopped on grid edge, some passengers can remain in active grids
|
||||
// unload all static passengers otherwise passengers won't load correctly when the grid that transport is currently in becomes active
|
||||
UnloadStaticPassengers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add model to map after we are fully done with moving maps
|
||||
@@ -259,49 +274,10 @@ void Transport::Update(uint32 diff)
|
||||
if (m_model)
|
||||
GetMap()->InsertGameObjectModel(*m_model);
|
||||
}
|
||||
|
||||
// Set position
|
||||
_positionChangeTimer.Update(diff);
|
||||
if (_positionChangeTimer.Passed())
|
||||
{
|
||||
_positionChangeTimer.Reset(positionUpdateDelay);
|
||||
if (IsMoving())
|
||||
{
|
||||
float t = !justStopped ? CalculateSegmentPos(float(timer) * 0.001f) : 1.0f;
|
||||
G3D::Vector3 pos, dir;
|
||||
_currentFrame->Spline->evaluate_percent(_currentFrame->Index, t, pos);
|
||||
_currentFrame->Spline->evaluate_derivative(_currentFrame->Index, t, dir);
|
||||
UpdatePosition(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI));
|
||||
}
|
||||
else if (justStopped)
|
||||
UpdatePosition(_currentFrame->Node->Loc.X, _currentFrame->Node->Loc.Y, _currentFrame->Node->Loc.Z, _currentFrame->InitialOrientation);
|
||||
else
|
||||
{
|
||||
/* There are four possible scenarios that trigger loading/unloading passengers:
|
||||
1. transport moves from inactive to active grid
|
||||
2. the grid that transport is currently in becomes active
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
bool gridActive = GetMap()->IsGridLoaded(GetPositionX(), GetPositionY());
|
||||
|
||||
if (_staticPassengers.empty() && gridActive) // 2.
|
||||
LoadStaticPassengers();
|
||||
else if (!_staticPassengers.empty() && !gridActive)
|
||||
// 4. - if transports stopped on grid edge, some passengers can remain in active grids
|
||||
// unload all static passengers otherwise passengers won't load correctly when the grid that transport is currently in becomes active
|
||||
UnloadStaticPassengers();
|
||||
}
|
||||
}
|
||||
|
||||
sScriptMgr->OnTransportUpdate(this, diff);
|
||||
}
|
||||
|
||||
void Transport::DelayedUpdate(uint32 /*diff*/)
|
||||
{
|
||||
if (GetKeyFrames().size() <= 1)
|
||||
return;
|
||||
|
||||
DelayedTeleportTransport();
|
||||
}
|
||||
|
||||
@@ -323,23 +299,7 @@ void Transport::AddPassenger(WorldObject* passenger)
|
||||
|
||||
Transport* Transport::RemovePassenger(WorldObject* passenger)
|
||||
{
|
||||
bool erased = false;
|
||||
if (_passengerTeleportItr != _passengers.end())
|
||||
{
|
||||
PassengerSet::iterator itr = _passengers.find(passenger);
|
||||
if (itr != _passengers.end())
|
||||
{
|
||||
if (itr == _passengerTeleportItr)
|
||||
++_passengerTeleportItr;
|
||||
|
||||
_passengers.erase(itr);
|
||||
erased = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
erased = _passengers.erase(passenger) > 0;
|
||||
|
||||
if (erased || _staticPassengers.erase(passenger)) // static passenger can remove itself in case of grid unload
|
||||
if (_passengers.erase(passenger) || _staticPassengers.erase(passenger)) // static passenger can remove itself in case of grid unload
|
||||
{
|
||||
passenger->SetTransport(nullptr);
|
||||
passenger->m_movementInfo.transport.Reset();
|
||||
@@ -574,6 +534,8 @@ int32 Transport::GetMapIdForSpawning() const
|
||||
|
||||
void Transport::UpdatePosition(float x, float y, float z, float o)
|
||||
{
|
||||
sScriptMgr->OnRelocate(this, GetMapId(), x, y, z);
|
||||
|
||||
bool newActive = GetMap()->IsGridLoaded(x, y);
|
||||
Cell oldCell(GetPositionX(), GetPositionY());
|
||||
|
||||
@@ -633,50 +595,16 @@ void Transport::EnableMovement(bool enabled)
|
||||
if (!GetGOInfo()->moTransport.allowstopping)
|
||||
return;
|
||||
|
||||
_pendingStop = !enabled;
|
||||
}
|
||||
|
||||
void Transport::MoveToNextWaypoint()
|
||||
{
|
||||
// Clear events flagging
|
||||
_triggeredArrivalEvent = false;
|
||||
_triggeredDepartureEvent = false;
|
||||
|
||||
// Set frames
|
||||
_currentFrame = _nextFrame++;
|
||||
if (_nextFrame == GetKeyFrames().end())
|
||||
_nextFrame = GetKeyFrames().begin();
|
||||
}
|
||||
|
||||
float Transport::CalculateSegmentPos(float now)
|
||||
{
|
||||
KeyFrame const& frame = *_currentFrame;
|
||||
const float speed = float(m_goInfo->moTransport.moveSpeed);
|
||||
const float accel = float(m_goInfo->moTransport.accelRate);
|
||||
float timeSinceStop = frame.TimeFrom + (now - (1.0f/IN_MILLISECONDS) * frame.DepartureTime);
|
||||
float timeUntilStop = frame.TimeTo - (now - (1.0f/IN_MILLISECONDS) * frame.DepartureTime);
|
||||
float segmentPos, dist;
|
||||
float accelTime = _transportInfo->accelTime;
|
||||
float accelDist = _transportInfo->accelDist;
|
||||
// calculate from nearest stop, less confusing calculation...
|
||||
if (timeSinceStop < timeUntilStop)
|
||||
if (!enabled)
|
||||
{
|
||||
if (timeSinceStop < accelTime)
|
||||
dist = 0.5f * accel * timeSinceStop * timeSinceStop;
|
||||
else
|
||||
dist = accelDist + (timeSinceStop - accelTime) * speed;
|
||||
segmentPos = dist - frame.DistSinceStop;
|
||||
_requestStopTimestamp = (_pathProgress / GetTransportPeriod()) * GetTransportPeriod() + _transportInfo->GetNextPauseWaypointTimestamp(_pathProgress);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timeUntilStop < _transportInfo->accelTime)
|
||||
dist = 0.5f * accel * timeUntilStop * timeUntilStop;
|
||||
else
|
||||
dist = accelDist + (timeUntilStop - accelTime) * speed;
|
||||
segmentPos = frame.DistUntilStop - dist;
|
||||
_requestStopTimestamp.reset();
|
||||
SetGoState(GO_STATE_ACTIVE);
|
||||
RemoveDynamicFlag(GO_DYNFLAG_LO_STOPPED);
|
||||
}
|
||||
|
||||
return segmentPos / frame.NextDistFromPrev;
|
||||
}
|
||||
|
||||
bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z, float o)
|
||||
@@ -685,7 +613,7 @@ bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z, fl
|
||||
|
||||
if (oldMap->GetId() != newMapid)
|
||||
{
|
||||
_delayedTeleport = true;
|
||||
_delayedTeleport.emplace(newMapid, x, y, z, o);
|
||||
UnloadStaticPassengers();
|
||||
return true;
|
||||
}
|
||||
@@ -720,20 +648,20 @@ void Transport::DelayedTeleportTransport()
|
||||
if (!_delayedTeleport)
|
||||
return;
|
||||
|
||||
_delayedTeleport = false;
|
||||
Map* newMap = sMapMgr->CreateBaseMap(_nextFrame->Node->ContinentID);
|
||||
Map* newMap = sMapMgr->CreateBaseMap(_delayedTeleport->GetMapId());
|
||||
GetMap()->RemoveFromMap<Transport>(this, false);
|
||||
SetMap(newMap);
|
||||
|
||||
float x = _nextFrame->Node->Loc.X,
|
||||
y = _nextFrame->Node->Loc.Y,
|
||||
z = _nextFrame->Node->Loc.Z,
|
||||
o =_nextFrame->InitialOrientation;
|
||||
float x = _delayedTeleport->GetPositionX(),
|
||||
y = _delayedTeleport->GetPositionY(),
|
||||
z = _delayedTeleport->GetPositionZ(),
|
||||
o = _delayedTeleport->GetOrientation();
|
||||
|
||||
for (_passengerTeleportItr = _passengers.begin(); _passengerTeleportItr != _passengers.end();)
|
||||
_delayedTeleport.reset();
|
||||
|
||||
PassengerSet passengersToTeleport = _passengers;
|
||||
for (WorldObject* obj : passengersToTeleport)
|
||||
{
|
||||
WorldObject* obj = (*_passengerTeleportItr++);
|
||||
|
||||
float destX, destY, destZ, destO;
|
||||
obj->m_movementInfo.transport.pos.GetPosition(destX, destY, destZ, destO);
|
||||
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, o);
|
||||
@@ -741,7 +669,7 @@ void Transport::DelayedTeleportTransport()
|
||||
switch (obj->GetTypeId())
|
||||
{
|
||||
case TYPEID_PLAYER:
|
||||
if (!obj->ToPlayer()->TeleportTo(_nextFrame->Node->ContinentID, destX, destY, destZ, destO, TELE_TO_NOT_LEAVE_TRANSPORT))
|
||||
if (!obj->ToPlayer()->TeleportTo(newMap->GetId(), destX, destY, destZ, destO, TELE_TO_NOT_LEAVE_TRANSPORT, newMap->GetInstanceId()))
|
||||
RemovePassenger(obj);
|
||||
break;
|
||||
case TYPEID_DYNAMICOBJECT:
|
||||
@@ -769,23 +697,15 @@ void Transport::UpdatePassengerPositions(PassengerSet const& passengers)
|
||||
}
|
||||
}
|
||||
|
||||
void Transport::DoEventIfAny(KeyFrame const& node, bool departure)
|
||||
{
|
||||
if (uint32 eventid = departure ? node.Node->DepartureEventID : node.Node->ArrivalEventID)
|
||||
{
|
||||
TC_LOG_DEBUG("maps.script", "Taxi %s event %u of node %u of %s path", departure ? "departure" : "arrival", eventid, node.Node->NodeIndex, GetName().c_str());
|
||||
GameEvents::Trigger(eventid, this, this);
|
||||
}
|
||||
}
|
||||
|
||||
void Transport::BuildUpdate(UpdateDataMapType& data_map)
|
||||
{
|
||||
Map::PlayerList const& players = GetMap()->GetPlayers();
|
||||
if (players.isEmpty())
|
||||
return;
|
||||
|
||||
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
||||
BuildFieldsUpdate(itr->GetSource(), data_map);
|
||||
for (MapReference const& playerReference : players)
|
||||
if (playerReference.GetSource()->IsInPhase(this))
|
||||
BuildFieldsUpdate(playerReference.GetSource(), data_map);
|
||||
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,12 +29,146 @@
|
||||
#include "Spline.h"
|
||||
#include "Transport.h"
|
||||
|
||||
bool KeyFrame::IsStopFrame() const
|
||||
TransportPathLeg::TransportPathLeg() = default;
|
||||
TransportPathLeg::~TransportPathLeg() = default;
|
||||
TransportPathLeg::TransportPathLeg(TransportPathLeg&&) noexcept = default;
|
||||
TransportPathLeg& TransportPathLeg::operator=(TransportPathLeg&&) noexcept = default;
|
||||
|
||||
TransportTemplate::TransportTemplate() = default;
|
||||
TransportTemplate::~TransportTemplate() = default;
|
||||
TransportTemplate::TransportTemplate(TransportTemplate&&) noexcept = default;
|
||||
TransportTemplate& TransportTemplate::operator=(TransportTemplate&&) noexcept = default;
|
||||
|
||||
Optional<Position> TransportTemplate::ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const
|
||||
{
|
||||
return (Node->Flags & TAXI_PATH_NODE_FLAG_STOP) != 0;
|
||||
time %= TotalPathTime;
|
||||
|
||||
// find leg
|
||||
auto legItr = PathLegs.begin();
|
||||
while (legItr->StartTimestamp + legItr->Duration <= time)
|
||||
{
|
||||
++legItr;
|
||||
|
||||
if (legItr == PathLegs.end())
|
||||
return {};
|
||||
}
|
||||
|
||||
// find segment
|
||||
uint32 prevSegmentTime = legItr->StartTimestamp;
|
||||
auto segmentItr = legItr->Segments.begin();
|
||||
double distanceMoved = 0.0;
|
||||
bool isOnPause = false;
|
||||
for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
|
||||
{
|
||||
if (time < segmentItr->SegmentEndArrivalTimestamp)
|
||||
break;
|
||||
|
||||
distanceMoved = segmentItr->DistanceFromLegStartAtEnd;
|
||||
if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
|
||||
{
|
||||
isOnPause = true;
|
||||
break;
|
||||
}
|
||||
|
||||
prevSegmentTime = segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
|
||||
}
|
||||
|
||||
if (!isOnPause)
|
||||
distanceMoved += CalculateDistanceMoved(
|
||||
double(time - prevSegmentTime) * 0.001,
|
||||
double(segmentItr->SegmentEndArrivalTimestamp - prevSegmentTime) * 0.001,
|
||||
segmentItr == legItr->Segments.begin(),
|
||||
segmentItr == std::prev(legItr->Segments.end()));
|
||||
|
||||
Movement::SplineBase::index_type splineIndex;
|
||||
float splinePointProgress;
|
||||
legItr->Spline->computeIndex(std::fmin(distanceMoved / legItr->Spline->length(), 1.0), splineIndex, splinePointProgress);
|
||||
|
||||
G3D::Vector3 pos, dir;
|
||||
legItr->Spline->evaluate_percent(splineIndex, splinePointProgress, pos);
|
||||
legItr->Spline->evaluate_derivative(splineIndex, splinePointProgress, dir);
|
||||
|
||||
if (moveState)
|
||||
*moveState = isOnPause ? TransportMovementState::WaitingOnPauseWaypoint : TransportMovementState::Moving;
|
||||
|
||||
if (legIndex)
|
||||
*legIndex = std::distance(PathLegs.begin(), legItr);
|
||||
|
||||
return Position(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI));
|
||||
}
|
||||
|
||||
TransportTemplate::~TransportTemplate() = default;
|
||||
double TransportTemplate::CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const
|
||||
{
|
||||
if (isFirstSegment)
|
||||
{
|
||||
if (!isLastSegment)
|
||||
{
|
||||
double accelerationTime = std::fmin(AccelerationTime, segmentDuration);
|
||||
double segmentTimeAtFullSpeed = segmentDuration - accelerationTime;
|
||||
if (timePassedInSegment <= segmentTimeAtFullSpeed)
|
||||
{
|
||||
return timePassedInSegment * Speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
double segmentAccelerationTime = timePassedInSegment - segmentTimeAtFullSpeed;
|
||||
double segmentAccelerationDistance = AccelerationRate * accelerationTime;
|
||||
double segmentDistanceAtFullSpeed = segmentTimeAtFullSpeed * Speed;
|
||||
return (2.0 * segmentAccelerationDistance - segmentAccelerationTime * AccelerationRate) * 0.5 * segmentAccelerationTime + segmentDistanceAtFullSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
return timePassedInSegment * Speed;
|
||||
}
|
||||
|
||||
if (isLastSegment)
|
||||
{
|
||||
if (!isFirstSegment)
|
||||
{
|
||||
if (timePassedInSegment <= std::fmin(AccelerationTime, segmentDuration))
|
||||
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
|
||||
else
|
||||
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
|
||||
}
|
||||
|
||||
return timePassedInSegment * Speed;
|
||||
}
|
||||
|
||||
double accelerationTime = std::fmin(segmentDuration * 0.5, AccelerationTime);
|
||||
if (timePassedInSegment <= segmentDuration - accelerationTime)
|
||||
{
|
||||
if (timePassedInSegment <= accelerationTime)
|
||||
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
|
||||
else
|
||||
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
double segmentTimeSpentAccelerating = timePassedInSegment - (segmentDuration - accelerationTime);
|
||||
return (segmentDuration - 2 * accelerationTime) * Speed
|
||||
+ AccelerationRate * accelerationTime * 0.5 * accelerationTime
|
||||
+ (2.0 * AccelerationRate * accelerationTime - segmentTimeSpentAccelerating * AccelerationRate) * 0.5 * segmentTimeSpentAccelerating;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 TransportTemplate::GetNextPauseWaypointTimestamp(uint32 time) const
|
||||
{
|
||||
auto legItr = PathLegs.begin();
|
||||
while (legItr->StartTimestamp + legItr->Duration <= time)
|
||||
{
|
||||
++legItr;
|
||||
|
||||
if (legItr == PathLegs.end())
|
||||
return time;
|
||||
}
|
||||
|
||||
auto segmentItr = legItr->Segments.begin();
|
||||
for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
|
||||
if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
|
||||
break;
|
||||
|
||||
return segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
|
||||
}
|
||||
|
||||
TransportMgr::TransportMgr() = default;
|
||||
|
||||
@@ -87,12 +221,14 @@ void TransportMgr::LoadTransportTemplates()
|
||||
|
||||
// paths are generated per template, saves us from generating it again in case of instanced transports
|
||||
TransportTemplate& transport = _transportTemplates[entry];
|
||||
transport.entry = entry;
|
||||
GeneratePath(goInfo, &transport);
|
||||
|
||||
std::set<uint32> mapsUsed;
|
||||
|
||||
GeneratePath(goInfo, &transport, &mapsUsed);
|
||||
|
||||
// transports in instance are only on one map
|
||||
if (transport.inInstance)
|
||||
_instanceTransports[*transport.mapsUsed.begin()].insert(entry);
|
||||
if (transport.InInstance)
|
||||
_instanceTransports[*mapsUsed.begin()].insert(entry);
|
||||
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
@@ -203,239 +339,206 @@ public:
|
||||
Movement::PointsArray& _points;
|
||||
};
|
||||
|
||||
void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport)
|
||||
static void InitializeLeg(TransportPathLeg* leg, std::vector<TransportPathEvent>* outEvents, std::vector<TaxiPathNodeEntry const*> const& pathPoints, std::vector<TaxiPathNodeEntry const*> const& pauses,
|
||||
std::vector<TaxiPathNodeEntry const*> const& events, GameObjectTemplate const* goInfo, uint32& totalTime)
|
||||
{
|
||||
Movement::PointsArray splinePath;
|
||||
std::transform(pathPoints.begin(), pathPoints.end(), std::back_inserter(splinePath), [](TaxiPathNodeEntry const* node) { return Movement::Vector3(node->Loc.X, node->Loc.Y, node->Loc.Z); });
|
||||
SplineRawInitializer initer(splinePath);
|
||||
leg->Spline = std::make_unique<TransportSpline>();
|
||||
leg->Spline->set_steps_per_segment(20);
|
||||
leg->Spline->init_spline_custom(initer);
|
||||
leg->Spline->initLengths();
|
||||
|
||||
leg->Segments.resize(pauses.size() + 1);
|
||||
|
||||
auto legTimeAccelDecel = [&](double dist)
|
||||
{
|
||||
double speed = double(goInfo->moTransport.moveSpeed);
|
||||
double accel = double(goInfo->moTransport.accelRate);
|
||||
double accelDist = 0.5 * speed * speed / accel;
|
||||
if (accelDist >= dist * 0.5)
|
||||
return uint32(std::sqrt(dist / accel) * 2000.0);
|
||||
else
|
||||
return uint32((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0);
|
||||
};
|
||||
|
||||
auto legTimeAccel = [&](double dist)
|
||||
{
|
||||
double speed = double(goInfo->moTransport.moveSpeed);
|
||||
double accel = double(goInfo->moTransport.accelRate);
|
||||
double accelDist = 0.5 * speed * speed / accel;
|
||||
if (accelDist >= dist)
|
||||
return uint32(std::sqrt((dist + dist) / accel) * 1000.0);
|
||||
else
|
||||
return uint32(((dist - accelDist) / speed + speed / accel) * 1000.0);
|
||||
};
|
||||
|
||||
// Init segments
|
||||
size_t pauseItr = 0;
|
||||
size_t eventItr = 0;
|
||||
double splineLengthToPreviousNode = 0.0;
|
||||
uint32 delaySum = 0;
|
||||
if (!pauses.empty())
|
||||
{
|
||||
for (; pauseItr < pauses.size(); ++pauseItr)
|
||||
{
|
||||
auto pausePointItr = std::find(pathPoints.begin(), pathPoints.end(), pauses[pauseItr]);
|
||||
if (*pausePointItr == pathPoints.back()) // last point is a "fake" spline point, its position can never be reached so transport cannot stop there
|
||||
break;
|
||||
|
||||
for (; eventItr < events.size(); ++eventItr)
|
||||
{
|
||||
auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
|
||||
if (eventPointItr > pausePointItr)
|
||||
break;
|
||||
|
||||
double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
|
||||
|
||||
uint32 splineTime = 0;
|
||||
if (pauseItr)
|
||||
splineTime = legTimeAccelDecel(length);
|
||||
else
|
||||
splineTime = legTimeAccel(length);
|
||||
|
||||
if ((*eventPointItr)->ArrivalEventID)
|
||||
{
|
||||
TransportPathEvent& event = outEvents->emplace_back();
|
||||
event.Timestamp = totalTime + splineTime + leg->Duration;
|
||||
event.EventId = (*eventPointItr)->ArrivalEventID;
|
||||
}
|
||||
|
||||
if ((*eventPointItr)->DepartureEventID)
|
||||
{
|
||||
TransportPathEvent& event = outEvents->emplace_back();
|
||||
event.Timestamp = totalTime + splineTime + leg->Duration + (pausePointItr == eventPointItr ? (*eventPointItr)->Delay * IN_MILLISECONDS : 0);
|
||||
event.EventId = (*eventPointItr)->DepartureEventID;
|
||||
}
|
||||
}
|
||||
|
||||
double splineLengthToCurrentNode = leg->Spline->length(std::distance(pathPoints.begin(), pausePointItr));
|
||||
double length = splineLengthToCurrentNode - splineLengthToPreviousNode;
|
||||
uint32 movementTime = 0;
|
||||
if (pauseItr)
|
||||
movementTime = legTimeAccelDecel(length);
|
||||
else
|
||||
movementTime = legTimeAccel(length);
|
||||
|
||||
leg->Duration += movementTime;
|
||||
leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration + delaySum;
|
||||
leg->Segments[pauseItr].Delay = (*pausePointItr)->Delay * IN_MILLISECONDS;
|
||||
leg->Segments[pauseItr].DistanceFromLegStartAtEnd = splineLengthToCurrentNode;
|
||||
delaySum += (*pausePointItr)->Delay * IN_MILLISECONDS;
|
||||
splineLengthToPreviousNode = splineLengthToCurrentNode;
|
||||
}
|
||||
}
|
||||
|
||||
// Process events happening after last pause
|
||||
for (; eventItr < events.size(); ++eventItr)
|
||||
{
|
||||
auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
|
||||
if (*eventPointItr == pathPoints.back()) // last point is a "fake" spline node, events cannot happen there
|
||||
break;
|
||||
|
||||
double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
|
||||
uint32 splineTime = 0;
|
||||
if (pauseItr)
|
||||
splineTime = legTimeAccel(length);
|
||||
else
|
||||
splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
|
||||
|
||||
if ((*eventPointItr)->ArrivalEventID)
|
||||
{
|
||||
TransportPathEvent& event = outEvents->emplace_back();
|
||||
event.Timestamp = totalTime + splineTime + leg->Duration;
|
||||
event.EventId = (*eventPointItr)->ArrivalEventID;
|
||||
}
|
||||
|
||||
if ((*eventPointItr)->DepartureEventID)
|
||||
{
|
||||
TransportPathEvent& event = outEvents->emplace_back();
|
||||
event.Timestamp = totalTime + splineTime + leg->Duration;
|
||||
event.EventId = (*eventPointItr)->DepartureEventID;
|
||||
}
|
||||
}
|
||||
|
||||
// Add segment after last pause
|
||||
double length = leg->Spline->length() - splineLengthToPreviousNode;
|
||||
uint32 splineTime = 0;
|
||||
if (pauseItr)
|
||||
splineTime = legTimeAccel(length);
|
||||
else
|
||||
splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
|
||||
|
||||
leg->StartTimestamp = totalTime;
|
||||
leg->Duration += splineTime + delaySum;
|
||||
leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration;
|
||||
leg->Segments[pauseItr].Delay = 0;
|
||||
leg->Segments[pauseItr].DistanceFromLegStartAtEnd = leg->Spline->length();
|
||||
totalTime += leg->Segments[pauseItr].SegmentEndArrivalTimestamp + leg->Segments[pauseItr].Delay;
|
||||
|
||||
for (TransportPathSegment& segment : leg->Segments)
|
||||
segment.SegmentEndArrivalTimestamp += leg->StartTimestamp;
|
||||
|
||||
if (leg->Segments.size() > pauseItr + 1)
|
||||
leg->Segments.resize(pauseItr + 1);
|
||||
}
|
||||
|
||||
void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed)
|
||||
{
|
||||
uint32 pathId = goInfo->moTransport.taxiPathID;
|
||||
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
|
||||
std::vector<KeyFrame>& keyFrames = transport->keyFrames;
|
||||
Movement::PointsArray splinePath, allPoints;
|
||||
bool mapChange = false;
|
||||
for (size_t i = 0; i < path.size(); ++i)
|
||||
allPoints.push_back(G3D::Vector3(path[i]->Loc.X, path[i]->Loc.Y, path[i]->Loc.Z));
|
||||
|
||||
// Add extra points to allow derivative calculations for all path nodes
|
||||
allPoints.insert(allPoints.begin(), allPoints.front().lerp(allPoints[1], -0.2f));
|
||||
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -0.2f));
|
||||
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -1.0f));
|
||||
transport->Speed = double(goInfo->moTransport.moveSpeed);
|
||||
transport->AccelerationRate = double(goInfo->moTransport.accelRate);
|
||||
transport->AccelerationTime = transport->Speed / transport->AccelerationRate;
|
||||
transport->AccelerationDistance = 0.5 * transport->Speed * transport->Speed / transport->AccelerationRate;
|
||||
|
||||
SplineRawInitializer initer(allPoints);
|
||||
TransportSpline orientationSpline;
|
||||
orientationSpline.init_spline_custom(initer);
|
||||
orientationSpline.initLengths();
|
||||
|
||||
for (size_t i = 0; i < path.size(); ++i)
|
||||
std::vector<TaxiPathNodeEntry const*> pathPoints;
|
||||
std::vector<TaxiPathNodeEntry const*> pauses;
|
||||
std::vector<TaxiPathNodeEntry const*> events;
|
||||
TransportPathLeg* leg = &transport->PathLegs.emplace_back();
|
||||
leg->MapId = path[0]->ContinentID;
|
||||
bool prevNodeWasTeleport = false;
|
||||
uint32 totalTime = 0;
|
||||
for (TaxiPathNodeEntry const* node : path)
|
||||
{
|
||||
if (!mapChange)
|
||||
if (node->ContinentID != leg->MapId || prevNodeWasTeleport)
|
||||
{
|
||||
TaxiPathNodeEntry const* node_i = path[i];
|
||||
if (i != path.size() - 1 && (node_i->Flags & TAXI_PATH_NODE_FLAG_TELEPORT || node_i->ContinentID != path[i + 1]->ContinentID))
|
||||
{
|
||||
keyFrames.back().Teleport = true;
|
||||
mapChange = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyFrame k(node_i);
|
||||
G3D::Vector3 h;
|
||||
orientationSpline.evaluate_derivative(i + 1, 0.0f, h);
|
||||
k.InitialOrientation = Position::NormalizeOrientation(std::atan2(h.y, h.x) + float(M_PI));
|
||||
InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
|
||||
|
||||
keyFrames.push_back(k);
|
||||
splinePath.push_back(G3D::Vector3(node_i->Loc.X, node_i->Loc.Y, node_i->Loc.Z));
|
||||
transport->mapsUsed.insert(k.Node->ContinentID);
|
||||
}
|
||||
leg = &transport->PathLegs.emplace_back();
|
||||
leg->MapId = node->ContinentID;
|
||||
pathPoints.clear();
|
||||
pauses.clear();
|
||||
events.clear();
|
||||
}
|
||||
else
|
||||
mapChange = false;
|
||||
|
||||
prevNodeWasTeleport = (node->Flags & TAXI_PATH_NODE_FLAG_TELEPORT) != 0;
|
||||
pathPoints.push_back(node);
|
||||
if (node->Flags & TAXI_PATH_NODE_FLAG_STOP)
|
||||
pauses.push_back(node);
|
||||
|
||||
if (node->ArrivalEventID || node->DepartureEventID)
|
||||
events.push_back(node);
|
||||
|
||||
mapsUsed->insert(node->ContinentID);
|
||||
}
|
||||
|
||||
if (splinePath.size() >= 2)
|
||||
if (!leg->Spline)
|
||||
InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
|
||||
|
||||
if (mapsUsed->size() > 1)
|
||||
{
|
||||
// Remove special catmull-rom spline points
|
||||
if (!keyFrames.front().IsStopFrame() && !keyFrames.front().Node->ArrivalEventID && !keyFrames.front().Node->DepartureEventID)
|
||||
{
|
||||
splinePath.erase(splinePath.begin());
|
||||
keyFrames.erase(keyFrames.begin());
|
||||
}
|
||||
if (!keyFrames.back().IsStopFrame() && !keyFrames.back().Node->ArrivalEventID && !keyFrames.back().Node->DepartureEventID)
|
||||
{
|
||||
splinePath.pop_back();
|
||||
keyFrames.pop_back();
|
||||
}
|
||||
}
|
||||
for (uint32 mapId : *mapsUsed)
|
||||
ASSERT(!sMapStore.LookupEntry(mapId)->Instanceable());
|
||||
|
||||
ASSERT(!keyFrames.empty());
|
||||
|
||||
if (transport->mapsUsed.size() > 1)
|
||||
{
|
||||
for (std::set<uint32>::const_iterator itr = transport->mapsUsed.begin(); itr != transport->mapsUsed.end(); ++itr)
|
||||
ASSERT(!sMapStore.LookupEntry(*itr)->Instanceable());
|
||||
|
||||
transport->inInstance = false;
|
||||
transport->InInstance = false;
|
||||
}
|
||||
else
|
||||
transport->inInstance = sMapStore.LookupEntry(*transport->mapsUsed.begin())->Instanceable();
|
||||
transport->InInstance = sMapStore.LookupEntry(*mapsUsed->begin())->Instanceable();
|
||||
|
||||
// last to first is always "teleport", even for closed paths
|
||||
keyFrames.back().Teleport = true;
|
||||
|
||||
const float speed = float(goInfo->moTransport.moveSpeed);
|
||||
const float accel = float(goInfo->moTransport.accelRate);
|
||||
const float accel_dist = 0.5f * speed * speed / accel;
|
||||
|
||||
transport->accelTime = speed / accel;
|
||||
transport->accelDist = accel_dist;
|
||||
|
||||
int32 firstStop = -1;
|
||||
int32 lastStop = -1;
|
||||
|
||||
// first cell is arrived at by teleportation :S
|
||||
keyFrames[0].DistFromPrev = 0;
|
||||
keyFrames[0].Index = 1;
|
||||
if (keyFrames[0].IsStopFrame())
|
||||
{
|
||||
firstStop = 0;
|
||||
lastStop = 0;
|
||||
}
|
||||
|
||||
// find the rest of the distances between key points
|
||||
// Every path segment has its own spline
|
||||
size_t start = 0;
|
||||
for (size_t i = 1; i < keyFrames.size(); ++i)
|
||||
{
|
||||
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.size())
|
||||
{
|
||||
size_t extra = !keyFrames[i - 1].Teleport ? 1 : 0;
|
||||
std::shared_ptr<TransportSpline> spline = std::make_shared<TransportSpline>();
|
||||
spline->init_spline(&splinePath[start], i - start + extra, Movement::SplineBase::ModeCatmullrom);
|
||||
spline->initLengths();
|
||||
for (size_t j = start; j < i + extra; ++j)
|
||||
{
|
||||
keyFrames[j].Index = j - start + 1;
|
||||
keyFrames[j].DistFromPrev = float(spline->length(j - start, j + 1 - start));
|
||||
if (j > 0)
|
||||
keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].Spline = spline;
|
||||
}
|
||||
|
||||
if (keyFrames[i - 1].Teleport)
|
||||
{
|
||||
keyFrames[i].Index = i - start + 1;
|
||||
keyFrames[i].DistFromPrev = 0.0f;
|
||||
keyFrames[i - 1].NextDistFromPrev = 0.0f;
|
||||
keyFrames[i].Spline = spline;
|
||||
}
|
||||
|
||||
start = i;
|
||||
}
|
||||
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
// remember first stop frame
|
||||
if (firstStop == -1)
|
||||
firstStop = i;
|
||||
lastStop = i;
|
||||
}
|
||||
}
|
||||
|
||||
keyFrames.back().NextDistFromPrev = keyFrames.front().DistFromPrev;
|
||||
|
||||
if (firstStop == -1 || lastStop == -1)
|
||||
firstStop = lastStop = 0;
|
||||
|
||||
// at stopping keyframes, we define distSinceStop == 0,
|
||||
// and distUntilStop is to the next stopping keyframe.
|
||||
// this is required to properly handle cases of two stopping frames in a row (yes they do exist)
|
||||
float tmpDist = 0.0f;
|
||||
for (size_t i = 0; i < keyFrames.size(); ++i)
|
||||
{
|
||||
int32 j = (i + lastStop) % keyFrames.size();
|
||||
if (keyFrames[j].IsStopFrame() || j == lastStop)
|
||||
tmpDist = 0.0f;
|
||||
else
|
||||
tmpDist += keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].DistSinceStop = tmpDist;
|
||||
}
|
||||
|
||||
tmpDist = 0.0f;
|
||||
for (int32 i = int32(keyFrames.size()) - 1; i >= 0; i--)
|
||||
{
|
||||
int32 j = (i + firstStop) % keyFrames.size();
|
||||
tmpDist += keyFrames[(j + 1) % keyFrames.size()].DistFromPrev;
|
||||
keyFrames[j].DistUntilStop = tmpDist;
|
||||
if (keyFrames[j].IsStopFrame() || j == firstStop)
|
||||
tmpDist = 0.0f;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < keyFrames.size(); ++i)
|
||||
{
|
||||
float total_dist = keyFrames[i].DistSinceStop + keyFrames[i].DistUntilStop;
|
||||
if (total_dist < 2 * accel_dist) // won't reach full speed
|
||||
{
|
||||
if (keyFrames[i].DistSinceStop < keyFrames[i].DistUntilStop) // is still accelerating
|
||||
{
|
||||
// calculate accel+brake time for this short segment
|
||||
float segment_time = 2.0f * std::sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel);
|
||||
// substract acceleration time
|
||||
keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
|
||||
}
|
||||
else // slowing down
|
||||
keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
|
||||
}
|
||||
else if (keyFrames[i].DistSinceStop < accel_dist) // still accelerating (but will reach full speed)
|
||||
{
|
||||
// calculate accel + cruise + brake time for this long segment
|
||||
float segment_time = (keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / speed + (speed / accel);
|
||||
// substract acceleration time
|
||||
keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
|
||||
}
|
||||
else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed)
|
||||
keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
|
||||
else // at full speed
|
||||
keyFrames[i].TimeTo = (keyFrames[i].DistUntilStop / speed) + (0.5f * speed / accel);
|
||||
}
|
||||
|
||||
// calculate tFrom times from tTo times
|
||||
float segmentTime = 0.0f;
|
||||
for (size_t i = 0; i < keyFrames.size(); ++i)
|
||||
{
|
||||
int32 j = (i + lastStop) % keyFrames.size();
|
||||
if (keyFrames[j].IsStopFrame() || j == lastStop)
|
||||
segmentTime = keyFrames[j].TimeTo;
|
||||
keyFrames[j].TimeFrom = segmentTime - keyFrames[j].TimeTo;
|
||||
}
|
||||
|
||||
// calculate path times
|
||||
keyFrames[0].ArriveTime = 0;
|
||||
float curPathTime = 0.0f;
|
||||
if (keyFrames[0].IsStopFrame())
|
||||
{
|
||||
curPathTime = float(keyFrames[0].Node->Delay);
|
||||
keyFrames[0].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < keyFrames.size(); ++i)
|
||||
{
|
||||
curPathTime += keyFrames[i - 1].TimeTo;
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
|
||||
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
|
||||
curPathTime += float(keyFrames[i].Node->Delay);
|
||||
keyFrames[i].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
curPathTime -= keyFrames[i].TimeTo;
|
||||
keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
|
||||
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
|
||||
keyFrames[i].DepartureTime = keyFrames[i].ArriveTime;
|
||||
}
|
||||
}
|
||||
|
||||
keyFrames.back().NextArriveTime = keyFrames.back().DepartureTime;
|
||||
|
||||
transport->pathTime = keyFrames.back().DepartureTime;
|
||||
transport->TotalPathTime = totalTime;
|
||||
}
|
||||
|
||||
void TransportMgr::AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node)
|
||||
@@ -477,16 +580,22 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Optional<Position> startingPosition = tInfo->ComputePosition(0, nullptr, nullptr);
|
||||
if (!startingPosition)
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Transport %u will not be loaded, failed to compute starting position", entry);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// create transport...
|
||||
Transport* trans = new Transport();
|
||||
|
||||
// ...at first waypoint
|
||||
TaxiPathNodeEntry const* startNode = tInfo->keyFrames.begin()->Node;
|
||||
uint32 mapId = startNode->ContinentID;
|
||||
float x = startNode->Loc.X;
|
||||
float y = startNode->Loc.Y;
|
||||
float z = startNode->Loc.Z;
|
||||
float o = tInfo->keyFrames.begin()->InitialOrientation;
|
||||
uint32 mapId = tInfo->PathLegs.front().MapId;
|
||||
float x = startingPosition->GetPositionX();
|
||||
float y = startingPosition->GetPositionY();
|
||||
float z = startingPosition->GetPositionZ();
|
||||
float o = startingPosition->GetOrientation();
|
||||
|
||||
// initialize the gameobject base
|
||||
ObjectGuid::LowType guidLow = guid ? guid : ASSERT_NOTNULL(map)->GenerateLowGuid<HighGuid::Transport>();
|
||||
@@ -500,7 +609,7 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
|
||||
|
||||
if (MapEntry const* mapEntry = sMapStore.LookupEntry(mapId))
|
||||
{
|
||||
if (mapEntry->Instanceable() != tInfo->inInstance)
|
||||
if (mapEntry->Instanceable() != tInfo->InInstance)
|
||||
{
|
||||
TC_LOG_ERROR("entities.transport", "Transport %u (name: %s) attempted creation in instance map (id: %u) but it is not an instanced transport!", entry, trans->GetName().c_str(), mapId);
|
||||
delete trans;
|
||||
@@ -525,7 +634,7 @@ void TransportMgr::SpawnContinentTransports()
|
||||
uint32 count = 0;
|
||||
|
||||
for (auto itr = _transportSpawns.begin(); itr != _transportSpawns.end(); ++itr)
|
||||
if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->inInstance)
|
||||
if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->InInstance)
|
||||
if (CreateTransport(itr->second.TransportGameObjectId, itr->second.SpawnId, nullptr, itr->second.PhaseUseFlags, itr->second.PhaseId, itr->second.PhaseGroup))
|
||||
++count;
|
||||
|
||||
|
||||
@@ -19,11 +19,12 @@
|
||||
#define TRANSPORTMGR_H
|
||||
|
||||
#include "ObjectGuid.h"
|
||||
#include "Optional.h"
|
||||
#include "Position.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
struct KeyFrame;
|
||||
struct GameObjectTemplate;
|
||||
struct TaxiPathNodeEntry;
|
||||
struct TransportAnimationEntry;
|
||||
@@ -38,49 +39,69 @@ namespace Movement
|
||||
}
|
||||
|
||||
using TransportSpline = Movement::Spline<double>;
|
||||
using KeyFrameVec = std::vector<KeyFrame>;
|
||||
|
||||
struct KeyFrame
|
||||
enum class TransportMovementState : uint8
|
||||
{
|
||||
explicit KeyFrame(TaxiPathNodeEntry const* node) : Index(0), Node(node), InitialOrientation(0.0f),
|
||||
DistSinceStop(-1.0f), DistUntilStop(-1.0f), DistFromPrev(-1.0f), TimeFrom(0.0f), TimeTo(0.0f),
|
||||
Teleport(false), ArriveTime(0), DepartureTime(0), Spline(nullptr), NextDistFromPrev(0.0f), NextArriveTime(0)
|
||||
{
|
||||
}
|
||||
Moving,
|
||||
WaitingOnPauseWaypoint
|
||||
};
|
||||
|
||||
uint32 Index;
|
||||
TaxiPathNodeEntry const* Node;
|
||||
float InitialOrientation;
|
||||
float DistSinceStop;
|
||||
float DistUntilStop;
|
||||
float DistFromPrev;
|
||||
float TimeFrom;
|
||||
float TimeTo;
|
||||
bool Teleport;
|
||||
uint32 ArriveTime;
|
||||
uint32 DepartureTime;
|
||||
std::shared_ptr<TransportSpline> Spline;
|
||||
// Represents a segment within path leg between stops
|
||||
struct TransportPathSegment
|
||||
{
|
||||
uint32 SegmentEndArrivalTimestamp = 0;
|
||||
uint32 Delay = 0;
|
||||
double DistanceFromLegStartAtEnd = 0.0;
|
||||
};
|
||||
|
||||
// Data needed for next frame
|
||||
float NextDistFromPrev;
|
||||
uint32 NextArriveTime;
|
||||
struct TransportPathEvent
|
||||
{
|
||||
uint32 Timestamp = 0;
|
||||
uint32 EventId = 0;
|
||||
};
|
||||
|
||||
bool IsTeleportFrame() const { return Teleport; }
|
||||
bool IsStopFrame() const;
|
||||
// Represents a contignuous part of transport path (without map changes or teleports)
|
||||
struct TransportPathLeg
|
||||
{
|
||||
TransportPathLeg();
|
||||
~TransportPathLeg();
|
||||
|
||||
TransportPathLeg(TransportPathLeg const&) = delete;
|
||||
TransportPathLeg(TransportPathLeg&&) noexcept;
|
||||
TransportPathLeg& operator=(TransportPathLeg const&) = delete;
|
||||
TransportPathLeg& operator=(TransportPathLeg&&) noexcept;
|
||||
|
||||
uint32 MapId = 0;
|
||||
std::unique_ptr<TransportSpline> Spline;
|
||||
uint32 StartTimestamp = 0;
|
||||
uint32 Duration = 0;
|
||||
std::vector<TransportPathSegment> Segments;
|
||||
};
|
||||
|
||||
struct TransportTemplate
|
||||
{
|
||||
TransportTemplate() : inInstance(false), pathTime(0), accelTime(0.0f), accelDist(0.0f), entry(0) { }
|
||||
TransportTemplate();
|
||||
~TransportTemplate();
|
||||
|
||||
std::set<uint32> mapsUsed;
|
||||
bool inInstance;
|
||||
uint32 pathTime;
|
||||
KeyFrameVec keyFrames;
|
||||
float accelTime;
|
||||
float accelDist;
|
||||
uint32 entry;
|
||||
TransportTemplate(TransportTemplate const&) = delete;
|
||||
TransportTemplate(TransportTemplate&&) noexcept;
|
||||
TransportTemplate& operator=(TransportTemplate const&) = delete;
|
||||
TransportTemplate& operator=(TransportTemplate&&) noexcept;
|
||||
|
||||
uint32 TotalPathTime = 0;
|
||||
double Speed = 0.0;
|
||||
double AccelerationRate = 0.0;
|
||||
double AccelerationTime = 0.0;
|
||||
double AccelerationDistance = 0.0;
|
||||
std::vector<TransportPathLeg> PathLegs;
|
||||
std::vector<TransportPathEvent> Events;
|
||||
|
||||
Optional<Position> ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const;
|
||||
uint32 GetNextPauseWaypointTimestamp(uint32 time) const;
|
||||
|
||||
double CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const;
|
||||
|
||||
bool InInstance = false;
|
||||
};
|
||||
|
||||
struct TC_GAME_API TransportAnimation
|
||||
@@ -142,7 +163,7 @@ class TC_GAME_API TransportMgr
|
||||
TransportMgr& operator=(TransportMgr const&) = delete;
|
||||
|
||||
// Generates and precaches a path for transport to avoid generation each time transport instance is created
|
||||
void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport);
|
||||
void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed);
|
||||
|
||||
void AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user