Core/Transports: Path generation rewrite v2 (needs testing)

Port From (https://github.com/TrinityCore/TrinityCore/commit/a2c1b699e6e4d30c752b65241bc23191920a86fd)
This commit is contained in:
hondacrx
2022-06-14 00:40:57 -04:00
parent 9c015e71fa
commit bbe99e3be8
10 changed files with 544 additions and 527 deletions
@@ -200,4 +200,10 @@ namespace Framework.Constants
SetTappedToChallengePlayers = 44, // Set Tapped to Challenge Players SetTappedToChallengePlayers = 44, // Set Tapped to Challenge Players
Max Max
} }
public enum TransportMovementState
{
Moving,
WaitingOnPauseWaypoint
}
} }
@@ -44,7 +44,7 @@ namespace Game.Entities
m_areaTriggerData = new AreaTriggerFieldData(); m_areaTriggerData = new AreaTriggerFieldData();
_spline = new Spline(); _spline = new();
} }
public override void AddToWorld() public override void AddToWorld()
@@ -756,7 +756,7 @@ namespace Game.Entities
_movementTime = 0; _movementTime = 0;
_spline.InitSpline(splinePoints.ToArray(), splinePoints.Count, Spline.EvaluationMode.Linear); _spline.InitSpline(splinePoints.ToArray(), splinePoints.Count, EvaluationMode.Linear);
_spline.InitLengths(); _spline.InitLengths();
// should be sent in object create packets only // should be sent in object create packets only
@@ -1078,7 +1078,7 @@ namespace Game.Entities
public Vector3 GetTargetRollPitchYaw() { return _targetRollPitchYaw; } public Vector3 GetTargetRollPitchYaw() { return _targetRollPitchYaw; }
public bool HasSplines() { return !_spline.Empty(); } public bool HasSplines() { return !_spline.Empty(); }
public Spline GetSpline() { return _spline; } public Spline<int> GetSpline() { return _spline; }
public uint GetElapsedTimeForMovement() { return GetTimeSinceCreated(); } // @todo: research the right value, in sniffs both timers are nearly identical public uint GetElapsedTimeForMovement() { return GetTimeSinceCreated(); } // @todo: research the right value, in sniffs both timers are nearly identical
public AreaTriggerOrbitInfo GetCircularMovementInfo() { return _orbitInfo; } public AreaTriggerOrbitInfo GetCircularMovementInfo() { return _orbitInfo; }
@@ -1102,7 +1102,7 @@ namespace Game.Entities
Vector3 _rollPitchYaw; Vector3 _rollPitchYaw;
Vector3 _targetRollPitchYaw; Vector3 _targetRollPitchYaw;
List<Vector2> _polygonVertices; List<Vector2> _polygonVertices;
Spline _spline; Spline<int> _spline;
bool _reachedDestination; bool _reachedDestination;
int _lastSplineIndex; int _lastSplineIndex;
@@ -126,22 +126,12 @@ namespace Game.Entities
dynFlags |= GameObjectDynamicLowFlags.Sparkle | GameObjectDynamicLowFlags.Highlight; dynFlags |= GameObjectDynamicLowFlags.Sparkle | GameObjectDynamicLowFlags.Highlight;
break; break;
case GameObjectTypes.Transport: case GameObjectTypes.Transport:
case GameObjectTypes.MapObjTransport:
{ {
dynFlags = (GameObjectDynamicLowFlags)((int)unitDynFlags & 0xFFFF); dynFlags = (GameObjectDynamicLowFlags)((int)unitDynFlags & 0xFFFF);
pathProgress = (ushort)((int)unitDynFlags >> 16); pathProgress = (ushort)((int)unitDynFlags >> 16);
break; break;
} }
case GameObjectTypes.MapObjTransport:
{
Transport transport = gameObject.ToTransport();
uint transportPeriod = transport.GetTransportPeriod();
if (transportPeriod != 0)
{
float timer = (float)(transport.GetTimer() % transportPeriod);
pathProgress = (ushort)(timer / (float)transportPeriod * 65535.0f);
}
break;
}
case GameObjectTypes.CapturePoint: case GameObjectTypes.CapturePoint:
if (!gameObject.CanInteractWithCapturePoint(receiver)) if (!gameObject.CanInteractWithCapturePoint(receiver))
dynFlags |= GameObjectDynamicLowFlags.NoInterract; dynFlags |= GameObjectDynamicLowFlags.NoInterract;
@@ -152,7 +142,7 @@ namespace Game.Entities
break; break;
} }
unitDynFlags = (uint)((pathProgress << 16) | (ushort)dynFlags); unitDynFlags = ((uint)pathProgress << 16) | (uint)dynFlags;
} }
} }
+98 -168
View File
@@ -19,6 +19,7 @@ using Framework.Constants;
using Game.DataStorage; using Game.DataStorage;
using Game.Maps; using Game.Maps;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
@@ -129,8 +130,6 @@ namespace Game.Entities
{ {
public Transport() public Transport()
{ {
_isMoving = true;
m_updateFlag.ServerTime = true; m_updateFlag.ServerTime = true;
m_updateFlag.Stationary = true; m_updateFlag.Stationary = true;
m_updateFlag.Rotation = true; m_updateFlag.Rotation = true;
@@ -175,10 +174,7 @@ namespace Game.Entities
} }
_transportInfo = tInfo; _transportInfo = tInfo;
_nextFrame = 0; _eventsToTrigger = new(tInfo.Events.Count, true);
_currentFrame = tInfo.keyFrames[_nextFrame++];
_triggeredArrivalEvent = false;
_triggeredDepartureEvent = false;
GameObjectOverride goOverride = GetGameObjectOverride(); GameObjectOverride goOverride = GetGameObjectOverride();
if (goOverride != null) if (goOverride != null)
@@ -187,13 +183,16 @@ namespace Game.Entities
ReplaceAllFlags(goOverride.Flags); ReplaceAllFlags(goOverride.Flags);
} }
_pathProgress = goinfo.MoTransport.allowstopping == 0 ? Time.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); SetObjectScale(goinfo.size);
SetPeriod(tInfo.pathTime); SetPeriod(tInfo.TotalPathTime);
SetEntry(goinfo.entry); SetEntry(goinfo.entry);
SetDisplayId(goinfo.displayId); SetDisplayId(goinfo.displayId);
SetGoState(goinfo.MoTransport.allowstopping == 0 ? GameObjectState.Ready : GameObjectState.Active); SetGoState(goinfo.MoTransport.allowstopping == 0 ? GameObjectState.Ready : GameObjectState.Active);
SetGoType(GameObjectTypes.MapObjTransport); SetGoType(GameObjectTypes.MapObjTransport);
SetGoAnimProgress(animprogress); SetGoAnimProgress(animprogress);
SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.SpawnTrackingStateAnimID), Global.DB2Mgr.GetEmptyAnimStateID());
SetName(goinfo.name); SetName(goinfo.name);
SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f); SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f);
SetParentRotation(Quaternion.Identity); SetParentRotation(Quaternion.Identity);
@@ -216,104 +215,85 @@ namespace Game.Entities
public override void Update(uint diff) public override void Update(uint diff)
{ {
uint positionUpdateDelay = 200; TimeSpan positionUpdateDelay = TimeSpan.FromMilliseconds(200);
if (GetAI() != null) if (GetAI() != null)
GetAI().UpdateAI(diff); GetAI().UpdateAI(diff);
else if (!AIM_Initialize()) else if (!AIM_Initialize())
Log.outError(LogFilter.Transport, "Could not initialize GameObjectAI for Transport"); Log.outError(LogFilter.Transport, "Could not initialize GameObjectAI for Transport");
if (GetKeyFrames().Count <= 1) Global.ScriptMgr.OnTransportUpdate(this, diff);
return;
if (IsMoving() || !_pendingStop) _positionChangeTimer.Update(diff);
uint cycleId = _pathProgress / GetTransportPeriod();
if (GetGoInfo().MoTransport.allowstopping == 0)
_pathProgress = GameTime.GetGameTimeMS();
else if (!_requestStopTimestamp.HasValue || _requestStopTimestamp > _pathProgress + diff)
_pathProgress += diff; _pathProgress += diff;
else
_pathProgress = _requestStopTimestamp.Value;
if (_pathProgress / GetTransportPeriod() != cycleId)
{
// reset cycle
_eventsToTrigger.SetAll(true);
}
SetPathProgressForClient((float)_pathProgress / (float)GetTransportPeriod());
uint timer = _pathProgress % GetTransportPeriod(); uint timer = _pathProgress % GetTransportPeriod();
bool justStopped = false;
// Set current waypoint int eventToTriggerIndex = -1;
// Desired outcome: _currentFrame.DepartureTime < timer < _nextFrame.ArriveTime for (var i = 0; i < _eventsToTrigger.Count; i++)
// ... arrive | ... delay ... | departure
// event / event /
for (; ; )
{ {
if (timer >= _currentFrame.ArriveTime) if (_eventsToTrigger.Get(i))
{ {
if (!_triggeredArrivalEvent) eventToTriggerIndex = i;
{ break;
DoEventIfAny(_currentFrame, false); }
_triggeredArrivalEvent = true;
} }
if (timer < _currentFrame.DepartureTime) if (eventToTriggerIndex != -1)
{ {
justStopped = IsMoving(); while (eventToTriggerIndex < _transportInfo.Events.Count && _transportInfo.Events[eventToTriggerIndex].Timestamp < timer)
SetMoving(false); {
if (_pendingStop && GetGoState() != GameObjectState.Ready) GameEvents.Trigger(_transportInfo.Events[eventToTriggerIndex].EventId, this, this);
_eventsToTrigger.Set(eventToTriggerIndex, false);
++eventToTriggerIndex;
}
}
TransportMovementState moveState;
int legIndex;
Position newPosition = _transportInfo.ComputePosition(timer, out moveState, out legIndex);
if (newPosition != null)
{
bool justStopped = _movementState == TransportMovementState.Moving && moveState != TransportMovementState.Moving;
_movementState = moveState;
if (justStopped)
{
if (_requestStopTimestamp != 0 && GetGoState() != GameObjectState.Ready)
{ {
SetGoState(GameObjectState.Ready); SetGoState(GameObjectState.Ready);
_pathProgress /= GetTransportPeriod(); SetDynamicFlag(GameObjectDynamicLowFlags.Stopped);
_pathProgress *= GetTransportPeriod();
_pathProgress += _currentFrame.ArriveTime;
}
break; // its a stop frame and we are waiting
} }
} }
if (timer >= _currentFrame.DepartureTime && !_triggeredDepartureEvent) if (legIndex != _currentPathLeg)
{ {
DoEventIfAny(_currentFrame, true); // departure event _currentPathLeg = legIndex;
_triggeredDepartureEvent = true; TeleportTransport(_transportInfo.PathLegs[legIndex].MapId, newPosition.GetPositionX(), newPosition.GetPositionY(), newPosition.GetPositionZ(), newPosition.GetOrientation());
}
// not waiting anymore
SetMoving(true);
// Enable movement
if (GetGoInfo().MoTransport.allowstopping != 0)
SetGoState(GameObjectState.Active);
if (timer >= _currentFrame.DepartureTime && timer < _currentFrame.NextArriveTime)
break; // found current waypoint
MoveToNextWaypoint();
Global.ScriptMgr.OnRelocate(this, (uint)_currentFrame.Node.NodeIndex, _currentFrame.Node.ContinentID, _currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z);
Log.outDebug(LogFilter.Transport, "Transport {0} ({1}) moved to node {2} {3} {4} {5} {6}", GetEntry(), GetName(), _currentFrame.Node.NodeIndex, _currentFrame.Node.ContinentID,
_currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z);
// Departure event
var nextframe = GetKeyFrames()[_nextFrame];
if (_currentFrame.IsTeleportFrame())
if (TeleportTransport(nextframe.Node.ContinentID, nextframe.Node.Loc.X, nextframe.Node.Loc.Y, nextframe.Node.Loc.Z, nextframe.InitialOrientation))
return; return;
} }
// Add model to map after we are fully done with moving maps // set position
if (_delayedAddModel)
{
_delayedAddModel = false;
if (m_model != null)
GetMap().InsertGameObjectModel(m_model);
}
// Set position
_positionChangeTimer.Update(diff);
if (_positionChangeTimer.Passed()) if (_positionChangeTimer.Passed())
{ {
_positionChangeTimer.Reset(positionUpdateDelay); _positionChangeTimer.Reset(positionUpdateDelay);
if (IsMoving()) if (_movementState == TransportMovementState.Moving || justStopped)
{ UpdatePosition(newPosition.GetPositionX(), newPosition.GetPositionY(), newPosition.GetPositionZ(), newPosition.GetOrientation());
float t = !justStopped ? CalculateSegmentPos(timer * 0.001f) : 1.0f;
Vector3 pos, dir;
_currentFrame.Spline.Evaluate_Percent((int)_currentFrame.Index, t, out pos);
_currentFrame.Spline.Evaluate_Derivative((int)_currentFrame.Index, t, out dir);
UpdatePosition(pos.X, pos.Y, pos.Z, (float)Math.Atan2(dir.Y, dir.X) + MathFunctions.PI);
}
else if (justStopped)
UpdatePosition(_currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z, _currentFrame.InitialOrientation);
else else
{ {
/* There are four possible scenarios that trigger loading/unloading passengers: /* There are four possible scenarios that trigger loading/unloading passengers:
@@ -332,15 +312,19 @@ namespace Game.Entities
UnloadStaticPassengers(); UnloadStaticPassengers();
} }
} }
}
Global.ScriptMgr.OnTransportUpdate(this, diff); // Add model to map after we are fully done with moving maps
if (_delayedAddModel)
{
_delayedAddModel = false;
if (m_model != null)
GetMap().InsertGameObjectModel(m_model);
}
} }
public void DelayedUpdate(uint diff) public void DelayedUpdate(uint diff)
{ {
if (GetKeyFrames().Count <= 1)
return;
DelayedTeleportTransport(); DelayedTeleportTransport();
} }
@@ -362,9 +346,7 @@ namespace Game.Entities
public ITransport RemovePassenger(WorldObject passenger) public ITransport RemovePassenger(WorldObject passenger)
{ {
bool erased = _passengers.Remove(passenger); if (_passengers.Remove(passenger) || _staticPassengers.Remove(passenger)) // static passenger can remove itself in case of grid unload
if (erased || _staticPassengers.Remove(passenger))
{ {
passenger.SetTransport(null); passenger.SetTransport(null);
passenger.m_movementInfo.transport.Reset(); passenger.m_movementInfo.transport.Reset();
@@ -592,6 +574,8 @@ namespace Game.Entities
public void UpdatePosition(float x, float y, float z, float o) public void UpdatePosition(float x, float y, float z, float o)
{ {
Global.ScriptMgr.OnRelocate(this, GetMapId(), x, y, z);
bool newActive = GetMap().IsGridLoaded(x, y); bool newActive = GetMap().IsGridLoaded(x, y);
Cell oldCell = new(GetPositionX(), GetPositionY()); Cell oldCell = new(GetPositionX(), GetPositionY());
@@ -648,61 +632,27 @@ namespace Game.Entities
if (GetGoInfo().MoTransport.allowstopping == 0) if (GetGoInfo().MoTransport.allowstopping == 0)
return; return;
_pendingStop = !enabled; if (!enabled)
{
_requestStopTimestamp = (_pathProgress / GetTransportPeriod()) * GetTransportPeriod() + _transportInfo.GetNextPauseWaypointTimestamp(_pathProgress);
}
else
{
_requestStopTimestamp = null;
SetGoState(GameObjectState.Active);
RemoveDynamicFlag(GameObjectDynamicLowFlags.Stopped);
}
} }
public void SetDelayedAddModelToMap() { _delayedAddModel = true; } public void SetDelayedAddModelToMap() { _delayedAddModel = true; }
void MoveToNextWaypoint()
{
// Clear events flagging
_triggeredArrivalEvent = false;
_triggeredDepartureEvent = false;
// Set frames
_currentFrame = GetKeyFrames()[_nextFrame++];
if (_nextFrame == GetKeyFrames().Count)
_nextFrame = 0;
}
float CalculateSegmentPos(float now)
{
KeyFrame frame = _currentFrame;
float speed = GetGoInfo().MoTransport.moveSpeed;
float accel = GetGoInfo().MoTransport.accelRate;
float timeSinceStop = frame.TimeFrom + (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime);
float timeUntilStop = frame.TimeTo - (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime);
float segmentPos, dist;
float accelTime = _transportInfo.accelTime;
float accelDist = _transportInfo.accelDist;
// calculate from nearest stop, less confusing calculation...
if (timeSinceStop < timeUntilStop)
{
if (timeSinceStop < accelTime)
dist = 0.5f * accel * timeSinceStop * timeSinceStop;
else
dist = accelDist + (timeSinceStop - accelTime) * speed;
segmentPos = dist - frame.DistSinceStop;
}
else
{
if (timeUntilStop < _transportInfo.accelTime)
dist = (0.5f * accel) * (timeUntilStop * timeUntilStop);
else
dist = accelDist + (timeUntilStop - accelTime) * speed;
segmentPos = frame.DistUntilStop - dist;
}
return segmentPos / frame.NextDistFromPrev;
}
bool TeleportTransport(uint newMapid, float x, float y, float z, float o) bool TeleportTransport(uint newMapid, float x, float y, float z, float o)
{ {
Map oldMap = GetMap(); Map oldMap = GetMap();
if (oldMap.GetId() != newMapid) if (oldMap.GetId() != newMapid)
{ {
_delayedTeleport = true; _delayedTeleport = new(newMapid, x, y, z, o);
UnloadStaticPassengers(); UnloadStaticPassengers();
return true; return true;
} }
@@ -735,22 +685,22 @@ namespace Game.Entities
void DelayedTeleportTransport() void DelayedTeleportTransport()
{ {
if (!_delayedTeleport) if (_delayedTeleport == null)
return; return;
var nextFrame = GetKeyFrames()[_nextFrame]; Map newMap = Global.MapMgr.CreateBaseMap(_delayedTeleport.GetMapId());
_delayedTeleport = false;
Map newMap = Global.MapMgr.CreateBaseMap(nextFrame.Node.ContinentID);
GetMap().RemoveFromMap(this, false); GetMap().RemoveFromMap(this, false);
SetMap(newMap); SetMap(newMap);
float x = nextFrame.Node.Loc.X, float x = _delayedTeleport.GetPositionX(),
y = nextFrame.Node.Loc.Y, y = _delayedTeleport.GetPositionY(),
z = nextFrame.Node.Loc.Z, z = _delayedTeleport.GetPositionZ(),
o = nextFrame.InitialOrientation; o = _delayedTeleport.GetOrientation();
foreach (WorldObject obj in _passengers.ToList()) _delayedTeleport = null;
List<WorldObject> passengersToTeleport = new(_passengers);
foreach (WorldObject obj in passengersToTeleport)
{ {
float destX, destY, destZ, destO; float destX, destY, destZ, destO;
obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO); obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO);
@@ -759,7 +709,7 @@ namespace Game.Entities
switch (obj.GetTypeId()) switch (obj.GetTypeId())
{ {
case TypeId.Player: case TypeId.Player:
if (!obj.ToPlayer().TeleportTo(nextFrame.Node.ContinentID, destX, destY, destZ, destO, TeleportToOptions.NotLeaveTransport)) if (!obj.ToPlayer().TeleportTo(newMap.GetId(), destX, destY, destZ, destO, TeleportToOptions.NotLeaveTransport, newMap.GetInstanceId()))
RemovePassenger(obj); RemovePassenger(obj);
break; break;
case TypeId.DynamicObject: case TypeId.DynamicObject:
@@ -787,24 +737,15 @@ namespace Game.Entities
} }
} }
void DoEventIfAny(KeyFrame node, bool departure)
{
uint eventid = departure ? node.Node.DepartureEventID : node.Node.ArrivalEventID;
if (eventid != 0)
{
Log.outDebug(LogFilter.Scripts, "Taxi {0} event {1} of node {2} of {3} path", departure ? "departure" : "arrival", eventid, node.Node.NodeIndex, GetName());
GameEvents.Trigger(eventid, this, this);
}
}
public override void BuildUpdate(Dictionary<Player, UpdateData> data_map) public override void BuildUpdate(Dictionary<Player, UpdateData> data_map)
{ {
var players = GetMap().GetPlayers(); var players = GetMap().GetPlayers();
if (players.Empty()) if (players.Empty())
return; return;
foreach (var pl in players) foreach (var playerReference in players)
BuildFieldsUpdate(pl, data_map); if (playerReference.IsInPhase(this))
BuildFieldsUpdate(playerReference, data_map);
ClearUpdateMask(true); ClearUpdateMask(true);
} }
@@ -818,30 +759,19 @@ namespace Game.Entities
public void SetPeriod(uint period) { SetLevel(period); } public void SetPeriod(uint period) { SetLevel(period); }
public uint GetTimer() { return _pathProgress; } public uint GetTimer() { return _pathProgress; }
public List<KeyFrame> GetKeyFrames() { return _transportInfo.keyFrames; }
public TransportTemplate GetTransportTemplate() { return _transportInfo; }
//! Helpers to know if stop frame was reached
bool IsMoving() { return _isMoving; }
void SetMoving(bool val) { _isMoving = val; }
TransportTemplate _transportInfo; TransportTemplate _transportInfo;
KeyFrame _currentFrame; TransportMovementState _movementState;
int _nextFrame; BitArray _eventsToTrigger;
int _currentPathLeg;
uint? _requestStopTimestamp;
uint _pathProgress; uint _pathProgress;
TimeTracker _positionChangeTimer = new(); TimeTracker _positionChangeTimer = new();
bool _isMoving;
bool _pendingStop;
//! These are needed to properly control events triggering only once for each frame
bool _triggeredArrivalEvent;
bool _triggeredDepartureEvent;
HashSet<WorldObject> _passengers = new(); HashSet<WorldObject> _passengers = new();
HashSet<WorldObject> _staticPassengers = new(); HashSet<WorldObject> _staticPassengers = new();
bool _delayedAddModel; bool _delayedAddModel;
bool _delayedTeleport; WorldLocation _delayedTeleport;
} }
} }
+378 -292
View File
@@ -71,14 +71,16 @@ namespace Game.Maps
// paths are generated per template, saves us from generating it again in case of instanced transports // paths are generated per template, saves us from generating it again in case of instanced transports
TransportTemplate transport = new(); TransportTemplate transport = new();
transport.entry = entry;
GeneratePath(goInfo, transport); List<uint> mapsUsed = new();
GeneratePath(goInfo, transport, mapsUsed);
_transportTemplates[entry] = transport; _transportTemplates[entry] = transport;
// transports in instance are only on one map // transports in instance are only on one map
if (transport.inInstance) if (transport.InInstance)
_instanceTransports.Add(transport.mapsUsed.First(), entry); _instanceTransports.Add(mapsUsed.First(), entry);
++count; ++count;
} while (result.NextRow()); } while (result.NextRow());
@@ -173,245 +175,209 @@ namespace Game.Maps
Log.outInfo(LogFilter.ServerLoading, $"Spawned {count} continent transports in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); Log.outInfo(LogFilter.ServerLoading, $"Spawned {count} continent transports in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
} }
void GeneratePath(GameObjectTemplate goInfo, TransportTemplate transport) static void InitializeLeg(TransportPathLeg leg, List<TransportPathEvent> outEvents, List<TaxiPathNodeRecord> pathPoints, List<TaxiPathNodeRecord> pauses, List<TaxiPathNodeRecord> events, GameObjectTemplate goInfo, ref uint totalTime)
{
List<Vector3> splinePath = new(pathPoints.Select(node => new Vector3(node.Loc.X, node.Loc.Y, node.Loc.Z)));
SplineRawInitializer initer = new(splinePath);
leg.Spline = new Spline<double>();
leg.Spline.set_steps_per_segment(20);
leg.Spline.InitSplineCustom(initer);
leg.Spline.InitLengths();
uint 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 (uint)(Math.Sqrt(dist / accel) * 2000.0);
else
return (uint)((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0);
}
uint 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 (uint)(Math.Sqrt((dist + dist) / accel) * 1000.0);
else
return (uint)(((dist - accelDist) / speed + speed / accel) * 1000.0);
};
// Init segments
int pauseItr = 0;
int eventItr = 0;
double splineLengthToPreviousNode = 0.0;
uint delaySum = 0;
if (!pauses.Empty())
{
for (; pauseItr < pauses.Count; ++pauseItr)
{
var pausePointIndex = pathPoints.IndexOf(pauses[pauseItr]);
if (pausePointIndex == -1) // last point is a "fake" spline point, its position can never be reached so transport cannot stop there
break;
for (; eventItr < events.Count; ++eventItr)
{
var eventPointIndex = pathPoints.IndexOf(events[eventItr]);
if (eventPointIndex > pausePointIndex)
break;
double eventLength = leg.Spline.Length(eventPointIndex) - splineLengthToPreviousNode;
uint eventSplineTime = 0;
if (pauseItr != 0)
eventSplineTime = legTimeAccelDecel(eventLength);
else
eventSplineTime = legTimeAccel(eventLength);
if (pathPoints[eventPointIndex].ArrivalEventID != 0)
{
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration;
Event.EventId = pathPoints[eventPointIndex].ArrivalEventID;
outEvents.Add(Event);
}
if (pathPoints[eventPointIndex].DepartureEventID != 0)
{
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration + (pausePointIndex == eventPointIndex ? pathPoints[eventPointIndex].Delay * Time.InMilliseconds : 0);
Event.EventId = pathPoints[eventPointIndex].DepartureEventID;
outEvents.Add(Event);
}
}
double splineLengthToCurrentNode = leg.Spline.Length(pausePointIndex);
double length1 = splineLengthToCurrentNode - splineLengthToPreviousNode;
uint movementTime = 0;
if (pauseItr != 0)
movementTime = legTimeAccelDecel(length1);
else
movementTime = legTimeAccel(length1);
leg.Duration += movementTime;
var segment = leg.Segments[pauseItr];
segment.SegmentEndArrivalTimestamp = leg.Duration + delaySum;
segment.Delay = pathPoints[pausePointIndex].Delay * Time.InMilliseconds;
segment.DistanceFromLegStartAtEnd = splineLengthToCurrentNode;
delaySum += pathPoints[pausePointIndex].Delay * Time.InMilliseconds;
splineLengthToPreviousNode = splineLengthToCurrentNode;
}
}
// Process events happening after last pause
for (; eventItr < events.Count; ++eventItr)
{
var eventPointIndex = pathPoints.IndexOf(events[eventItr]);
if (eventPointIndex == -1) // last point is a "fake" spline node, events cannot happen there
break;
double eventLength = leg.Spline.Length(eventPointIndex) - splineLengthToPreviousNode;
uint eventSplineTime = 0;
if (pauseItr != 0)
eventSplineTime = legTimeAccel(eventLength);
else
eventSplineTime = (uint)(eventLength / (double)goInfo.MoTransport.moveSpeed * 1000.0);
if (pathPoints[eventPointIndex].ArrivalEventID != 0)
{
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration;
Event.EventId = pathPoints[eventPointIndex].ArrivalEventID;
outEvents.Add(Event);
}
if (pathPoints[eventPointIndex].DepartureEventID != 0)
{
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration;
Event.EventId = pathPoints[eventPointIndex].DepartureEventID;
outEvents.Add(Event);
}
}
// Add segment after last pause
double length = leg.Spline.Length() - splineLengthToPreviousNode;
uint splineTime = 0;
if (pauseItr != 0)
splineTime = legTimeAccel(length);
else
splineTime = (uint)(length / (double)goInfo.MoTransport.moveSpeed * 1000.0);
leg.StartTimestamp = totalTime;
leg.Duration += splineTime + delaySum;
var pauseSegment = leg.Segments[pauseItr];
pauseSegment.SegmentEndArrivalTimestamp = leg.Duration;
pauseSegment.Delay = 0;
pauseSegment.DistanceFromLegStartAtEnd = leg.Spline.Length();
totalTime += leg.Segments[pauseItr].SegmentEndArrivalTimestamp + leg.Segments[pauseItr].Delay;
for (var i = 0; i < leg.Segments.Count; ++i)
{
var segment = leg.Segments[i];
segment.SegmentEndArrivalTimestamp += leg.StartTimestamp;
}
}
void GeneratePath(GameObjectTemplate goInfo, TransportTemplate transport, List<uint> mapsUsed)
{ {
uint pathId = goInfo.MoTransport.taxiPathID; uint pathId = goInfo.MoTransport.taxiPathID;
var path = CliDB.TaxiPathNodesByPath[pathId]; TaxiPathNodeRecord[] path = CliDB.TaxiPathNodesByPath[pathId];
List<KeyFrame> keyFrames = transport.keyFrames;
List<Vector3> splinePath = new();
List<Vector3> allPoints = new();
bool mapChange = false;
for (uint i = 0; i < path.Length; ++i) transport.Speed = (double)goInfo.MoTransport.moveSpeed;
allPoints.Add(new Vector3(path[i].Loc.X, path[i].Loc.Y, path[i].Loc.Z)); transport.AccelerationRate = (double)goInfo.MoTransport.accelRate;
transport.AccelerationTime = transport.Speed / transport.AccelerationRate;
transport.AccelerationDistance = 0.5 * transport.Speed * transport.Speed / transport.AccelerationRate;
// Add extra points to allow derivative calculations for all path nodes List<TaxiPathNodeRecord> pathPoints = new();
allPoints.Insert(0, Vector3.Lerp(allPoints.First(), allPoints[1], -0.2f)); List<TaxiPathNodeRecord> pauses = new();
allPoints.Add(Vector3.Lerp(allPoints.Last(), allPoints[^2], -0.2f)); List<TaxiPathNodeRecord> events = new();
allPoints.Add(Vector3.Lerp(allPoints.Last(), allPoints[^2], -1.0f)); TransportPathLeg leg = new();
leg.MapId = path[0].ContinentID;
SplineRawInitializer initer = new(allPoints); bool prevNodeWasTeleport = false;
Spline orientationSpline = new(); uint totalTime = 0;
orientationSpline.InitSplineCustom(initer); foreach (TaxiPathNodeRecord node in path)
orientationSpline.InitLengths();
for (uint i = 0; i < path.Length; ++i)
{ {
if (!mapChange) if (node.ContinentID != leg.MapId || prevNodeWasTeleport)
{ {
var node_i = path[i]; InitializeLeg(leg, transport.Events, pathPoints, pauses, events, goInfo, ref totalTime);
if (i != path.Length - 1 && (node_i.Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport) || node_i.ContinentID != path[i + 1].ContinentID))
{
keyFrames.Last().Teleport = true;
mapChange = true;
}
else
{
KeyFrame k = new(node_i);
Vector3 h;
orientationSpline.Evaluate_Derivative((int)(i + 1), 0.0f, out h);
k.InitialOrientation = Position.NormalizeOrientation((float)Math.Atan2(h.Y, h.X) + MathFunctions.PI);
keyFrames.Add(k); leg = new();
splinePath.Add(new Vector3(node_i.Loc.X, node_i.Loc.Y, node_i.Loc.Z)); leg.MapId = node.ContinentID;
if (!transport.mapsUsed.Contains(k.Node.ContinentID)) pathPoints.Clear();
transport.mapsUsed.Add(k.Node.ContinentID); pauses.Clear();
} events.Clear();
} transport.PathLegs.Add(leg);
else
mapChange = false;
} }
if (splinePath.Count >= 2) prevNodeWasTeleport = node.Flags.HasFlag(TaxiPathNodeFlags.Teleport);
{ pathPoints.Add(node);
// Remove special catmull-rom spline points if (node.Flags.HasFlag(TaxiPathNodeFlags.Stop))
if (!keyFrames.First().IsStopFrame() && keyFrames.First().Node.ArrivalEventID == 0 && keyFrames.First().Node.DepartureEventID == 0) pauses.Add(node);
{
splinePath.RemoveAt(0); if (node.ArrivalEventID != 0 || node.DepartureEventID != 0)
keyFrames.RemoveAt(0); events.Add(node);
}
if (!keyFrames.Last().IsStopFrame() && keyFrames.Last().Node.ArrivalEventID == 0 && keyFrames.Last().Node.DepartureEventID == 0) mapsUsed.Add(node.ContinentID);
{
splinePath.RemoveAt(splinePath.Count - 1);
keyFrames.RemoveAt(keyFrames.Count - 1);
}
} }
Cypher.Assert(!keyFrames.Empty()); if (leg.Spline == null)
InitializeLeg(leg, transport.Events, pathPoints, pauses, events, goInfo, ref totalTime);
if (transport.mapsUsed.Count > 1) if (mapsUsed.Count > 1)
{ {
foreach (var mapId in transport.mapsUsed) foreach (uint mapId in mapsUsed)
Cypher.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable()); Cypher.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable());
transport.inInstance = false; transport.InInstance = false;
} }
else else
transport.inInstance = CliDB.MapStorage.LookupByKey(transport.mapsUsed.First()).Instanceable(); transport.InInstance = CliDB.MapStorage.LookupByKey(mapsUsed.First()).Instanceable();
// last to first is always "teleport", even for closed paths transport.TotalPathTime = totalTime;
keyFrames.Last().Teleport = true; transport.PathLegs.Add(leg);
float speed = goInfo.MoTransport.moveSpeed;
float accel = goInfo.MoTransport.accelRate;
float accel_dist = 0.5f * speed * speed / accel;
transport.accelTime = speed / accel;
transport.accelDist = accel_dist;
int firstStop = -1;
int 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
int start = 0;
for (int i = 1; i < keyFrames.Count; ++i)
{
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.Count)
{
int extra = !keyFrames[i - 1].Teleport ? 1 : 0;
Spline spline = new();
Span<Vector3> span = splinePath.ToArray();
spline.InitSpline(span[start..], i - start + extra, Spline.EvaluationMode.Catmullrom);
spline.InitLengths();
for (int j = start; j < i + extra; ++j)
{
keyFrames[j].Index = (uint)(j - start + 1);
keyFrames[j].DistFromPrev = 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 = (uint)(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.Last().NextDistFromPrev = keyFrames.First().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 (int i = 0; i < keyFrames.Count; ++i)
{
int j = (i + lastStop) % keyFrames.Count;
if (keyFrames[j].IsStopFrame() || j == lastStop)
tmpDist = 0.0f;
else
tmpDist += keyFrames[j].DistFromPrev;
keyFrames[j].DistSinceStop = tmpDist;
}
tmpDist = 0.0f;
for (int i = (keyFrames.Count - 1); i >= 0; i--)
{
int j = (i + firstStop) % keyFrames.Count;
tmpDist += keyFrames[(j + 1) % keyFrames.Count].DistFromPrev;
keyFrames[j].DistUntilStop = tmpDist;
if (keyFrames[j].IsStopFrame() || j == firstStop)
tmpDist = 0.0f;
}
for (int i = 0; i < keyFrames.Count; ++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 * (float)Math.Sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel);
// substract acceleration time
keyFrames[i].TimeTo = segment_time - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel);
}
else // slowing down
keyFrames[i].TimeTo = (float)Math.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 - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel);
}
else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed)
keyFrames[i].TimeTo = (float)Math.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 (int i = 0; i < keyFrames.Count; ++i)
{
int j = (i + lastStop) % keyFrames.Count;
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 = keyFrames[0].Node.Delay;
keyFrames[0].DepartureTime = (uint)(curPathTime * Time.InMilliseconds);
}
for (int i = 1; i < keyFrames.Count; ++i)
{
curPathTime += keyFrames[i - 1].TimeTo;
if (keyFrames[i].IsStopFrame())
{
keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds);
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
curPathTime += keyFrames[i].Node.Delay;
keyFrames[i].DepartureTime = (uint)(curPathTime * Time.InMilliseconds);
}
else
{
curPathTime -= keyFrames[i].TimeTo;
keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds);
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
keyFrames[i].DepartureTime = keyFrames[i].ArriveTime;
}
}
keyFrames.Last().NextArriveTime = keyFrames.Last().DepartureTime;
transport.pathTime = keyFrames.Last().DepartureTime;
if (transport.pathTime == 0)
{
}
} }
public void AddPathNodeToTransport(uint transportEntry, uint timeSeg, TransportAnimationRecord node) public void AddPathNodeToTransport(uint transportEntry, uint timeSeg, TransportAnimationRecord node)
@@ -461,16 +427,22 @@ namespace Game.Maps
return null; return null;
} }
Position startingPosition = tInfo.ComputePosition(0, out _, out _);
if (startingPosition == null)
{
Log.outError(LogFilter.Sql, $"Transport {entry} will not be loaded, failed to compute starting position");
return null;
}
// create transport... // create transport...
Transport trans = new(); Transport trans = new();
// ...at first waypoint // ...at first waypoint
TaxiPathNodeRecord startNode = tInfo.keyFrames.First().Node; uint mapId = tInfo.PathLegs.First().MapId;
uint mapId = startNode.ContinentID; float x = startingPosition.GetPositionX();
float x = startNode.Loc.X; float y = startingPosition.GetPositionY();
float y = startNode.Loc.Y; float z = startingPosition.GetPositionZ();
float z = startNode.Loc.Z; float o = startingPosition.GetOrientation();
float o = tInfo.keyFrames.First().InitialOrientation;
// initialize the gameobject base // initialize the gameobject base
ulong guidLow = guid != 0 ? guid : map.GenerateLowGuid(HighGuid.Transport); ulong guidLow = guid != 0 ? guid : map.GenerateLowGuid(HighGuid.Transport);
@@ -482,7 +454,7 @@ namespace Game.Maps
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId);
if (mapEntry != null) if (mapEntry != null)
{ {
if (mapEntry.Instanceable() != tInfo.inInstance) if (mapEntry.Instanceable() != tInfo.InInstance)
{ {
Log.outError(LogFilter.Transport, "Transport {0} (name: {1}) attempted creation in instance map (id: {2}) but it is not an instanced transport!", entry, trans.GetName(), mapId); Log.outError(LogFilter.Transport, "Transport {0} (name: {1}) attempted creation in instance map (id: {2}) but it is not an instanced transport!", entry, trans.GetName(), mapId);
//return null; //return null;
@@ -508,7 +480,7 @@ namespace Game.Maps
uint count = 0; uint count = 0;
foreach (var pair in _transportSpawns) foreach (var pair in _transportSpawns)
if (!GetTransportTemplate(pair.Value.TransportGameObjectId).inInstance) if (!GetTransportTemplate(pair.Value.TransportGameObjectId).InInstance)
if (CreateTransport(pair.Value.TransportGameObjectId, pair.Value.SpawnId, null, pair.Value.PhaseUseFlags, pair.Value.PhaseId, pair.Value.PhaseGroup)) if (CreateTransport(pair.Value.TransportGameObjectId, pair.Value.SpawnId, null, pair.Value.PhaseUseFlags, pair.Value.PhaseId, pair.Value.PhaseGroup))
++count; ++count;
@@ -549,6 +521,177 @@ namespace Game.Maps
Dictionary<ulong, TransportSpawn> _transportSpawns = new(); Dictionary<ulong, TransportSpawn> _transportSpawns = new();
} }
public struct TransportPathSegment
{
public uint SegmentEndArrivalTimestamp;
public uint Delay;
public double DistanceFromLegStartAtEnd;
}
public struct TransportPathEvent
{
public uint Timestamp;
public uint EventId;
}
public class TransportPathLeg
{
public uint MapId;
public Spline<double> Spline;
public uint StartTimestamp;
public uint Duration;
public List<TransportPathSegment> Segments = new();
}
public class TransportTemplate
{
public uint TotalPathTime;
public double Speed;
public double AccelerationRate;
public double AccelerationTime;
public double AccelerationDistance;
public List<TransportPathLeg> PathLegs = new();
public List<TransportPathEvent> Events = new();
public bool InInstance;
public Position ComputePosition(uint time, out TransportMovementState moveState, out int legIndex)
{
moveState = TransportMovementState.Moving;
time %= TotalPathTime;
// find leg
legIndex = 0;
while (PathLegs[legIndex].StartTimestamp + PathLegs[legIndex].Duration <= time)
{
++legIndex;
if (PathLegs.Count >= legIndex)
return null;
}
var legItr = PathLegs[legIndex];
// find segment
uint prevSegmentTime = legItr.StartTimestamp;
var segmentIndex = 0;
double distanceMoved = 0.0;
bool isOnPause = false;
for (segmentIndex = 0; segmentIndex < legItr.Segments.Count; ++segmentIndex)
{
var segment = legItr.Segments[segmentIndex];
if (time < segment.SegmentEndArrivalTimestamp)
break;
distanceMoved = segment.DistanceFromLegStartAtEnd;
if (time < segment.SegmentEndArrivalTimestamp + segment.Delay)
{
isOnPause = true;
break;
}
prevSegmentTime = segment.SegmentEndArrivalTimestamp + segment.Delay;
}
var pathSegment = legItr.Segments[segmentIndex];
if (!isOnPause)
distanceMoved += CalculateDistanceMoved(
(double)(time - prevSegmentTime) * 0.001,
(double)(pathSegment.SegmentEndArrivalTimestamp - prevSegmentTime) * 0.001,
segmentIndex == 0,
segmentIndex == legItr.Segments.Count);
int splineIndex = 0;
float splinePointProgress = 0;
legItr.Spline.ComputeIndex((float)Math.Min(distanceMoved / legItr.Spline.Length(), 1.0), ref splineIndex, ref splinePointProgress);
Vector3 pos, dir;
legItr.Spline.Evaluate_Percent(splineIndex, splinePointProgress, out pos);
legItr.Spline.Evaluate_Derivative(splineIndex, splinePointProgress, out dir);
moveState = isOnPause ? TransportMovementState.WaitingOnPauseWaypoint : TransportMovementState.Moving;
return new Position(pos.X, pos.Y, pos.Z, MathF.Atan2(dir.Y, dir.X) + MathF.PI);
}
double CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment)
{
if (isFirstSegment)
{
if (!isLastSegment)
{
double accelerationTime = Math.Min(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 <= Math.Min(AccelerationTime, segmentDuration))
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
else
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
}
return timePassedInSegment * Speed;
}
double accelerationTime1 = Math.Min(segmentDuration * 0.5, AccelerationTime);
if (timePassedInSegment <= segmentDuration - accelerationTime1)
{
if (timePassedInSegment <= accelerationTime1)
return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
else
return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
}
else
{
double segmentTimeSpentAccelerating = timePassedInSegment - (segmentDuration - accelerationTime1);
return (segmentDuration - 2 * accelerationTime1) * Speed
+ AccelerationRate * accelerationTime1 * 0.5 * accelerationTime1
+ (2.0 * AccelerationRate * accelerationTime1 - segmentTimeSpentAccelerating * AccelerationRate) * 0.5 * segmentTimeSpentAccelerating;
}
}
public uint GetNextPauseWaypointTimestamp(uint time)
{
var legIndex = 0;
while (PathLegs[legIndex].StartTimestamp + PathLegs[legIndex].Duration <= time)
{
++legIndex;
if (legIndex >= PathLegs.Count)
return time;
}
var leg = PathLegs[legIndex];
var segmentIndex = 0;
for (; segmentIndex != leg.Segments.Count - 1; ++segmentIndex)
if (time < leg.Segments[segmentIndex].SegmentEndArrivalTimestamp + leg.Segments[segmentIndex].Delay)
break;
return leg.Segments[segmentIndex].SegmentEndArrivalTimestamp + leg.Segments[segmentIndex].Delay;
}
}
public class SplineRawInitializer public class SplineRawInitializer
{ {
public SplineRawInitializer(List<Vector3> points) public SplineRawInitializer(List<Vector3> points)
@@ -556,9 +699,9 @@ namespace Game.Maps
_points = points; _points = points;
} }
public void Initialize(ref Spline.EvaluationMode mode, ref bool cyclic, ref Vector3[] points, ref int lo, ref int hi) public void Initialize(ref EvaluationMode mode, ref bool cyclic, ref Vector3[] points, ref int lo, ref int hi)
{ {
mode = Spline.EvaluationMode.Catmullrom; mode = EvaluationMode.Catmullrom;
cyclic = false; cyclic = false;
points = new Vector3[_points.Count]; points = new Vector3[_points.Count];
@@ -572,63 +715,6 @@ namespace Game.Maps
List<Vector3> _points; List<Vector3> _points;
} }
public class KeyFrame
{
public KeyFrame(TaxiPathNodeRecord _node)
{
Node = _node;
DistSinceStop = -1.0f;
DistUntilStop = -1.0f;
DistFromPrev = -1.0f;
TimeFrom = 0.0f;
TimeTo = 0.0f;
Teleport = false;
ArriveTime = 0;
DepartureTime = 0;
Spline = null;
NextDistFromPrev = 0.0f;
NextArriveTime = 0;
}
public uint Index;
public TaxiPathNodeRecord Node;
public float InitialOrientation;
public float DistSinceStop;
public float DistUntilStop;
public float DistFromPrev;
public float TimeFrom;
public float TimeTo;
public bool Teleport;
public uint ArriveTime;
public uint DepartureTime;
public Spline Spline;
// Data needed for next frame
public float NextDistFromPrev;
public uint NextArriveTime;
public bool IsTeleportFrame() { return Teleport; }
public bool IsStopFrame() { return Node.Flags.HasAnyFlag(TaxiPathNodeFlags.Stop); }
}
public class TransportTemplate
{
public TransportTemplate()
{
pathTime = 0;
accelTime = 0.0f;
accelDist = 0.0f;
}
public List<uint> mapsUsed = new();
public bool inInstance;
public uint pathTime;
public List<KeyFrame> keyFrames = new();
public float accelTime;
public float accelDist;
public uint entry;
}
public class TransportAnimation public class TransportAnimation
{ {
public Dictionary<uint, TransportAnimationRecord> Path = new(); public Dictionary<uint, TransportAnimationRecord> Path = new();
+11 -8
View File
@@ -87,7 +87,7 @@ namespace Game.Movement
void InitSpline(MoveSplineInitArgs args) void InitSpline(MoveSplineInitArgs args)
{ {
Spline.EvaluationMode[] modes = new Spline.EvaluationMode[2] { Spline.EvaluationMode.Linear, Spline.EvaluationMode.Catmullrom }; EvaluationMode[] modes = new EvaluationMode[2] { EvaluationMode.Linear, EvaluationMode.Catmullrom };
if (args.flags.HasFlag(SplineFlag.Cyclic)) if (args.flags.HasFlag(SplineFlag.Cyclic))
{ {
int cyclic_point = 0; int cyclic_point = 0;
@@ -351,7 +351,7 @@ namespace Game.Movement
#region Fields #region Fields
public MoveSplineInitArgs InitArgs; public MoveSplineInitArgs InitArgs;
public Spline spline = new(); public Spline<int> spline = new();
public FacingInfo facing; public FacingInfo facing;
public MoveSplineFlag splineflags = new(); public MoveSplineFlag splineflags = new();
public bool onTransport; public bool onTransport;
@@ -368,7 +368,7 @@ namespace Game.Movement
public AnimTierTransition anim_tier; public AnimTierTransition anim_tier;
#endregion #endregion
public class CommonInitializer : IInitializer public class CommonInitializer : IInitializer<int>
{ {
public CommonInitializer(float _velocity) public CommonInitializer(float _velocity)
{ {
@@ -377,20 +377,23 @@ namespace Game.Movement
} }
public float velocityInv; public float velocityInv;
public int time; public int time;
public int SetGetTime(Spline s, int i)
public int Invoke(Spline<int> s, int i)
{ {
time += (int)(s.SegLength(i) * velocityInv); time += (int)(s.SegLength(i) * velocityInv);
return time; return time;
} }
} }
public class FallInitializer : IInitializer
public class FallInitializer : IInitializer<int>
{ {
public FallInitializer(float startelevation) public FallInitializer(float startelevation)
{ {
startElevation = startelevation; startElevation = startelevation;
} }
float startElevation; float startElevation;
public int SetGetTime(Spline s, int i)
public int Invoke(Spline<int> s, int i)
{ {
return (int)(ComputeFallTime(startElevation - s.GetPoint(i + 1).Z, false) * 1000.0f); return (int)(ComputeFallTime(startElevation - s.GetPoint(i + 1).Z, false) * 1000.0f);
} }
@@ -427,9 +430,9 @@ namespace Game.Movement
NextSegment = 0x08 NextSegment = 0x08
} }
} }
public interface IInitializer public interface IInitializer<T>
{ {
int SetGetTime(Spline s, int i); int Invoke(Spline<T> s, int i);
} }
public class SplineChainLink public class SplineChainLink
+29 -27
View File
@@ -23,7 +23,7 @@ using System.Numerics;
namespace Game.Movement namespace Game.Movement
{ {
public class Spline public class Spline<T>
{ {
public int GetPointCount() { return points.Length; } public int GetPointCount() { return points.Length; }
public Vector3 GetPoint(int i) { return points[i]; } public Vector3 GetPoint(int i) { return points[i]; }
@@ -262,18 +262,18 @@ namespace Game.Movement
} }
#endregion #endregion
void set_steps_per_segment(int newStepsPerSegment) { stepsPerSegment = newStepsPerSegment; } public void set_steps_per_segment(int newStepsPerSegment) { stepsPerSegment = newStepsPerSegment; }
public void ComputeIndex(float t, ref int index, ref float u) public void ComputeIndex(float t, ref int index, ref float u)
{ {
//ASSERT(t >= 0.f && t <= 1.f); //ASSERT(t >= 0.f && t <= 1.f);
int length_ = (int)(t * Length()); T length_ = t * (dynamic)Length();
index = ComputeIndexInBounds(length_); index = ComputeIndexInBounds(length_);
//ASSERT(index < index_hi); //ASSERT(index < index_hi);
u = (length_ - Length(index)) / (float)Length(index, index + 1); u = (length_ - Length(index)) / (float)Length(index, index + 1);
} }
int ComputeIndexInBounds(int length_) int ComputeIndexInBounds(T length_)
{ {
// Temporary disabled: causes infinite loop with t = 1.f // Temporary disabled: causes infinite loop with t = 1.f
/* /*
@@ -294,11 +294,12 @@ namespace Game.Movement
int i = index_lo; int i = index_lo;
int N = index_hi; int N = index_hi;
while (i + 1 < N && lengths[i + 1] < length_) while (i + 1 < N && (dynamic)lengths[i + 1] < length_)
++i; ++i;
return i; return i;
} }
private static readonly Matrix4x4 s_catmullRomCoeffs = new(-0.5f, 1.5f, -1.5f, 0.5f, 1.0f, -2.5f, 2.0f, -0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); private static readonly Matrix4x4 s_catmullRomCoeffs = new(-0.5f, 1.5f, -1.5f, 0.5f, 1.0f, -2.5f, 2.0f, -0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
private static readonly Matrix4x4 s_Bezier3Coeffs = new(-1.0f, 3.0f, -3.0f, 1.0f, 3.0f, -6.0f, 3.0f, 0.0f, -3.0f, 3.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); private static readonly Matrix4x4 s_Bezier3Coeffs = new(-1.0f, 3.0f, -3.0f, 1.0f, 3.0f, -6.0f, 3.0f, 0.0f, -3.0f, 3.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
@@ -320,31 +321,31 @@ namespace Game.Movement
+ vertice[2] * weights.Z + vertice[3] * weights.W; + vertice[2] * weights.Z + vertice[3] * weights.W;
} }
public int Length() public dynamic Length()
{ {
if (lengths.Length == 0) if (lengths.Length == 0)
return 0; return default;
return lengths[index_hi]; return lengths[index_hi];
} }
public int Length(int first, int last) { return lengths[last] - lengths[first]; } public dynamic Length(int first, int last) { return lengths[last] - (dynamic)lengths[first]; }
public int Length(int Idx) { return lengths[Idx]; } public dynamic Length(int Idx) { return lengths[Idx]; }
public void Set_length(int i, int length) { lengths[i] = length; } public void Set_length(int i, T length) { lengths[i] = length; }
public void InitLengths(IInitializer cacher) public void InitLengths(IInitializer<T> cacher)
{ {
int i = index_lo; int i = index_lo;
Array.Resize(ref lengths, index_hi+1); Array.Resize(ref lengths, index_hi+1);
int prev_length; T prev_length;
int new_length; T new_length;
while (i < index_hi) while (i < index_hi)
{ {
new_length = cacher.SetGetTime(this, i); new_length = (dynamic)cacher.Invoke(this, i);
if (new_length < 0) if ((dynamic)new_length < 0)// todo fix me this is a ulgy hack.
new_length = int.MaxValue; new_length = (dynamic)(Type.GetTypeCode(typeof(T)) == TypeCode.Int32 ? int.MaxValue : double.MaxValue);
lengths[++i] = new_length; lengths[++i] = new_length;
prev_length = new_length; prev_length = new_length;
@@ -354,18 +355,18 @@ namespace Game.Movement
public void InitLengths() public void InitLengths()
{ {
int i = index_lo; int i = index_lo;
int length = 0; dynamic length = default(T);
Array.Resize(ref lengths, index_hi + 1); Array.Resize(ref lengths, index_hi + 1);
while (i < index_hi) while (i < index_hi)
{ {
length += (int)SegLength(i); length += SegLength(i);
lengths[++i] = length; lengths[++i] = length;
} }
} }
public bool Empty() { return index_lo == index_hi;} public bool Empty() { return index_lo == index_hi;}
int[] lengths = Array.Empty<int>(); T[] lengths = Array.Empty<T>();
Vector3[] points = Array.Empty<Vector3>(); Vector3[] points = Array.Empty<Vector3>();
public EvaluationMode m_mode; public EvaluationMode m_mode;
bool _cyclic; bool _cyclic;
@@ -379,14 +380,6 @@ namespace Game.Movement
int index_lo; int index_lo;
int index_hi; int index_hi;
public enum EvaluationMode
{
Linear,
Catmullrom,
Bezier3_Unused,
UninitializedMode,
ModesEnd
}
} }
public class FacingInfo public class FacingInfo
@@ -396,4 +389,13 @@ namespace Game.Movement
public float angle; public float angle;
public MonsterMoveType type; public MonsterMoveType type;
} }
public enum EvaluationMode
{
Linear,
Catmullrom,
Bezier3_Unused,
UninitializedMode,
ModesEnd
}
} }
@@ -301,7 +301,7 @@ namespace Game.Networking.Packets
} }
} }
public static void WriteCreateObjectAreaTriggerSpline(Spline spline, WorldPacket data) public static void WriteCreateObjectAreaTriggerSpline(Spline<int> spline, WorldPacket data)
{ {
data.WriteBits(spline.GetPoints().Length, 16); data.WriteBits(spline.GetPoints().Length, 16);
foreach (var point in spline.GetPoints()) foreach (var point in spline.GetPoints())
@@ -425,7 +425,7 @@ namespace Game.Networking.Packets
movementSpline.SpellEffectExtraData = spellEffectExtraData; movementSpline.SpellEffectExtraData = spellEffectExtraData;
} }
Spline spline = moveSpline.spline; var spline = moveSpline.spline;
Vector3[] array = spline.GetPoints(); Vector3[] array = spline.GetPoints();
if (splineFlags.HasFlag(SplineFlag.UncompressedPath)) if (splineFlags.HasFlag(SplineFlag.UncompressedPath))
+1 -1
View File
@@ -575,7 +575,7 @@ namespace Game.Scripting
public virtual void OnRemovePassenger(Transport transport, Player player) { } public virtual void OnRemovePassenger(Transport transport, Player player) { }
// Called when a transport moves. // Called when a transport moves.
public virtual void OnRelocate(Transport transport, uint waypointId, uint mapId, float x, float y, float z) { } public virtual void OnRelocate(Transport transport, uint mapId, float x, float y, float z) { }
public virtual void OnUpdate(Transport obj, uint diff) { } public virtual void OnUpdate(Transport obj, uint diff) { }
} }
+2 -2
View File
@@ -863,9 +863,9 @@ namespace Game.Scripting
RunScript<TransportScript>(p => p.OnUpdate(transport, diff), transport.GetScriptId()); RunScript<TransportScript>(p => p.OnUpdate(transport, diff), transport.GetScriptId());
} }
public void OnRelocate(Transport transport, uint waypointId, uint mapId, float x, float y, float z) public void OnRelocate(Transport transport, uint mapId, float x, float y, float z)
{ {
RunScript<TransportScript>(p => p.OnRelocate(transport, waypointId, mapId, x, y, z), transport.GetScriptId()); RunScript<TransportScript>(p => p.OnRelocate(transport, mapId, x, y, z), transport.GetScriptId());
} }
// Achievement // Achievement