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
Max
}
public enum TransportMovementState
{
Moving,
WaitingOnPauseWaypoint
}
}
@@ -44,7 +44,7 @@ namespace Game.Entities
m_areaTriggerData = new AreaTriggerFieldData();
_spline = new Spline();
_spline = new();
}
public override void AddToWorld()
@@ -756,7 +756,7 @@ namespace Game.Entities
_movementTime = 0;
_spline.InitSpline(splinePoints.ToArray(), splinePoints.Count, Spline.EvaluationMode.Linear);
_spline.InitSpline(splinePoints.ToArray(), splinePoints.Count, EvaluationMode.Linear);
_spline.InitLengths();
// should be sent in object create packets only
@@ -1078,7 +1078,7 @@ namespace Game.Entities
public Vector3 GetTargetRollPitchYaw() { return _targetRollPitchYaw; }
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 AreaTriggerOrbitInfo GetCircularMovementInfo() { return _orbitInfo; }
@@ -1102,7 +1102,7 @@ namespace Game.Entities
Vector3 _rollPitchYaw;
Vector3 _targetRollPitchYaw;
List<Vector2> _polygonVertices;
Spline _spline;
Spline<int> _spline;
bool _reachedDestination;
int _lastSplineIndex;
@@ -126,22 +126,12 @@ namespace Game.Entities
dynFlags |= GameObjectDynamicLowFlags.Sparkle | GameObjectDynamicLowFlags.Highlight;
break;
case GameObjectTypes.Transport:
case GameObjectTypes.MapObjTransport:
{
dynFlags = (GameObjectDynamicLowFlags)((int)unitDynFlags & 0xFFFF);
pathProgress = (ushort)((int)unitDynFlags >> 16);
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:
if (!gameObject.CanInteractWithCapturePoint(receiver))
dynFlags |= GameObjectDynamicLowFlags.NoInterract;
@@ -152,7 +142,7 @@ namespace Game.Entities
break;
}
unitDynFlags = (uint)((pathProgress << 16) | (ushort)dynFlags);
unitDynFlags = ((uint)pathProgress << 16) | (uint)dynFlags;
}
}
+113 -183
View File
@@ -19,6 +19,7 @@ using Framework.Constants;
using Game.DataStorage;
using Game.Maps;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
@@ -129,8 +130,6 @@ namespace Game.Entities
{
public Transport()
{
_isMoving = true;
m_updateFlag.ServerTime = true;
m_updateFlag.Stationary = true;
m_updateFlag.Rotation = true;
@@ -175,10 +174,7 @@ namespace Game.Entities
}
_transportInfo = tInfo;
_nextFrame = 0;
_currentFrame = tInfo.keyFrames[_nextFrame++];
_triggeredArrivalEvent = false;
_triggeredDepartureEvent = false;
_eventsToTrigger = new(tInfo.Events.Count, true);
GameObjectOverride goOverride = GetGameObjectOverride();
if (goOverride != null)
@@ -187,13 +183,16 @@ namespace Game.Entities
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);
SetPeriod(tInfo.pathTime);
SetPeriod(tInfo.TotalPathTime);
SetEntry(goinfo.entry);
SetDisplayId(goinfo.displayId);
SetGoState(goinfo.MoTransport.allowstopping == 0 ? GameObjectState.Ready : GameObjectState.Active);
SetGoType(GameObjectTypes.MapObjTransport);
SetGoAnimProgress(animprogress);
SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.SpawnTrackingStateAnimID), Global.DB2Mgr.GetEmptyAnimStateID());
SetName(goinfo.name);
SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f);
SetParentRotation(Quaternion.Identity);
@@ -216,79 +215,103 @@ namespace Game.Entities
public override void Update(uint diff)
{
uint positionUpdateDelay = 200;
TimeSpan positionUpdateDelay = TimeSpan.FromMilliseconds(200);
if (GetAI() != null)
GetAI().UpdateAI(diff);
else if (!AIM_Initialize())
Log.outError(LogFilter.Transport, "Could not initialize GameObjectAI for Transport");
if (GetKeyFrames().Count <= 1)
return;
Global.ScriptMgr.OnTransportUpdate(this, diff);
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;
else
_pathProgress = _requestStopTimestamp.Value;
if (_pathProgress / GetTransportPeriod() != cycleId)
{
// reset cycle
_eventsToTrigger.SetAll(true);
}
SetPathProgressForClient((float)_pathProgress / (float)GetTransportPeriod());
uint timer = _pathProgress % GetTransportPeriod();
bool justStopped = false;
// Set current waypoint
// Desired outcome: _currentFrame.DepartureTime < timer < _nextFrame.ArriveTime
// ... arrive | ... delay ... | departure
// event / event /
for (; ; )
int eventToTriggerIndex = -1;
for (var i = 0; i < _eventsToTrigger.Count; i++)
{
if (timer >= _currentFrame.ArriveTime)
if (_eventsToTrigger.Get(i))
{
if (!_triggeredArrivalEvent)
{
DoEventIfAny(_currentFrame, false);
_triggeredArrivalEvent = true;
}
eventToTriggerIndex = i;
break;
}
}
if (timer < _currentFrame.DepartureTime)
if (eventToTriggerIndex != -1)
{
while (eventToTriggerIndex < _transportInfo.Events.Count && _transportInfo.Events[eventToTriggerIndex].Timestamp < timer)
{
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)
{
justStopped = IsMoving();
SetMoving(false);
if (_pendingStop && GetGoState() != GameObjectState.Ready)
{
SetGoState(GameObjectState.Ready);
_pathProgress /= GetTransportPeriod();
_pathProgress *= GetTransportPeriod();
_pathProgress += _currentFrame.ArriveTime;
}
break; // its a stop frame and we are waiting
SetGoState(GameObjectState.Ready);
SetDynamicFlag(GameObjectDynamicLowFlags.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 != 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;
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
@@ -298,49 +321,10 @@ namespace Game.Entities
if (m_model != null)
GetMap().InsertGameObjectModel(m_model);
}
// Set position
_positionChangeTimer.Update(diff);
if (_positionChangeTimer.Passed())
{
_positionChangeTimer.Reset(positionUpdateDelay);
if (IsMoving())
{
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
{
/* 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();
}
}
Global.ScriptMgr.OnTransportUpdate(this, diff);
}
public void DelayedUpdate(uint diff)
{
if (GetKeyFrames().Count <= 1)
return;
DelayedTeleportTransport();
}
@@ -362,9 +346,7 @@ namespace Game.Entities
public ITransport RemovePassenger(WorldObject passenger)
{
bool erased = _passengers.Remove(passenger);
if (erased || _staticPassengers.Remove(passenger))
if (_passengers.Remove(passenger) || _staticPassengers.Remove(passenger)) // static passenger can remove itself in case of grid unload
{
passenger.SetTransport(null);
passenger.m_movementInfo.transport.Reset();
@@ -589,9 +571,11 @@ namespace Game.Entities
{
return GetGoInfo().MoTransport.SpawnMap;
}
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);
Cell oldCell = new(GetPositionX(), GetPositionY());
@@ -648,61 +632,27 @@ namespace Game.Entities
if (GetGoInfo().MoTransport.allowstopping == 0)
return;
_pendingStop = !enabled;
}
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 (!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 = null;
SetGoState(GameObjectState.Active);
RemoveDynamicFlag(GameObjectDynamicLowFlags.Stopped);
}
return segmentPos / frame.NextDistFromPrev;
}
public void SetDelayedAddModelToMap() { _delayedAddModel = true; }
bool TeleportTransport(uint newMapid, float x, float y, float z, float o)
{
Map oldMap = GetMap();
if (oldMap.GetId() != newMapid)
{
_delayedTeleport = true;
_delayedTeleport = new(newMapid, x, y, z, o);
UnloadStaticPassengers();
return true;
}
@@ -735,22 +685,22 @@ namespace Game.Entities
void DelayedTeleportTransport()
{
if (!_delayedTeleport)
if (_delayedTeleport == null)
return;
var nextFrame = GetKeyFrames()[_nextFrame];
_delayedTeleport = false;
Map newMap = Global.MapMgr.CreateBaseMap(nextFrame.Node.ContinentID);
Map newMap = Global.MapMgr.CreateBaseMap(_delayedTeleport.GetMapId());
GetMap().RemoveFromMap(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();
foreach (WorldObject obj in _passengers.ToList())
_delayedTeleport = null;
List<WorldObject> passengersToTeleport = new(_passengers);
foreach (WorldObject obj in passengersToTeleport)
{
float destX, destY, destZ, 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())
{
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);
break;
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)
{
var players = GetMap().GetPlayers();
if (players.Empty())
return;
foreach (var pl in players)
BuildFieldsUpdate(pl, data_map);
foreach (var playerReference in players)
if (playerReference.IsInPhase(this))
BuildFieldsUpdate(playerReference, data_map);
ClearUpdateMask(true);
}
@@ -813,35 +754,24 @@ namespace Game.Entities
public ObjectGuid GetTransportGUID() { return GetGUID(); }
public float GetTransportOrientation() { return GetOrientation(); }
public uint GetTransportPeriod() { return m_gameObjectData.Level; }
public void SetPeriod(uint period) { SetLevel(period); }
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;
KeyFrame _currentFrame;
int _nextFrame;
TransportMovementState _movementState;
BitArray _eventsToTrigger;
int _currentPathLeg;
uint? _requestStopTimestamp;
uint _pathProgress;
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> _staticPassengers = new();
bool _delayedAddModel;
bool _delayedTeleport;
WorldLocation _delayedTeleport;
}
}
+374 -288
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
TransportTemplate transport = new();
transport.entry = entry;
GeneratePath(goInfo, transport);
List<uint> mapsUsed = new();
GeneratePath(goInfo, transport, mapsUsed);
_transportTemplates[entry] = transport;
// transports in instance are only on one map
if (transport.inInstance)
_instanceTransports.Add(transport.mapsUsed.First(), entry);
if (transport.InInstance)
_instanceTransports.Add(mapsUsed.First(), entry);
++count;
} while (result.NextRow());
@@ -173,247 +175,211 @@ namespace Game.Maps
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)
{
uint pathId = goInfo.MoTransport.taxiPathID;
var path = CliDB.TaxiPathNodesByPath[pathId];
List<KeyFrame> keyFrames = transport.keyFrames;
List<Vector3> splinePath = new();
List<Vector3> allPoints = new();
bool mapChange = false;
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();
for (uint i = 0; i < path.Length; ++i)
allPoints.Add(new 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(0, Vector3.Lerp(allPoints.First(), allPoints[1], -0.2f));
allPoints.Add(Vector3.Lerp(allPoints.Last(), allPoints[^2], -0.2f));
allPoints.Add(Vector3.Lerp(allPoints.Last(), allPoints[^2], -1.0f));
SplineRawInitializer initer = new(allPoints);
Spline orientationSpline = new();
orientationSpline.InitSplineCustom(initer);
orientationSpline.InitLengths();
for (uint i = 0; i < path.Length; ++i)
uint legTimeAccelDecel(double dist)
{
if (!mapChange)
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 node_i = path[i];
if (i != path.Length - 1 && (node_i.Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport) || node_i.ContinentID != path[i + 1].ContinentID))
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)
{
keyFrames.Last().Teleport = true;
mapChange = true;
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
{
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);
movementTime = legTimeAccel(length1);
keyFrames.Add(k);
splinePath.Add(new Vector3(node_i.Loc.X, node_i.Loc.Y, node_i.Loc.Z));
if (!transport.mapsUsed.Contains(k.Node.ContinentID))
transport.mapsUsed.Add(k.Node.ContinentID);
}
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
mapChange = false;
}
eventSplineTime = (uint)(eventLength / (double)goInfo.MoTransport.moveSpeed * 1000.0);
if (splinePath.Count >= 2)
{
// Remove special catmull-rom spline points
if (!keyFrames.First().IsStopFrame() && keyFrames.First().Node.ArrivalEventID == 0 && keyFrames.First().Node.DepartureEventID == 0)
if (pathPoints[eventPointIndex].ArrivalEventID != 0)
{
splinePath.RemoveAt(0);
keyFrames.RemoveAt(0);
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration;
Event.EventId = pathPoints[eventPointIndex].ArrivalEventID;
outEvents.Add(Event);
}
if (!keyFrames.Last().IsStopFrame() && keyFrames.Last().Node.ArrivalEventID == 0 && keyFrames.Last().Node.DepartureEventID == 0)
if (pathPoints[eventPointIndex].DepartureEventID != 0)
{
splinePath.RemoveAt(splinePath.Count - 1);
keyFrames.RemoveAt(keyFrames.Count - 1);
TransportPathEvent Event = new();
Event.Timestamp = totalTime + eventSplineTime + leg.Duration;
Event.EventId = pathPoints[eventPointIndex].DepartureEventID;
outEvents.Add(Event);
}
}
Cypher.Assert(!keyFrames.Empty());
if (transport.mapsUsed.Count > 1)
{
foreach (var mapId in transport.mapsUsed)
Cypher.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable());
transport.inInstance = false;
}
// Add segment after last pause
double length = leg.Spline.Length() - splineLengthToPreviousNode;
uint splineTime = 0;
if (pauseItr != 0)
splineTime = legTimeAccel(length);
else
transport.inInstance = CliDB.MapStorage.LookupByKey(transport.mapsUsed.First()).Instanceable();
splineTime = (uint)(length / (double)goInfo.MoTransport.moveSpeed * 1000.0);
// last to first is always "teleport", even for closed paths
keyFrames.Last().Teleport = true;
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;
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())
for (var i = 0; i < leg.Segments.Count; ++i)
{
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)
{
var segment = leg.Segments[i];
segment.SegmentEndArrivalTimestamp += leg.StartTimestamp;
}
}
void GeneratePath(GameObjectTemplate goInfo, TransportTemplate transport, List<uint> mapsUsed)
{
uint pathId = goInfo.MoTransport.taxiPathID;
TaxiPathNodeRecord[] path = CliDB.TaxiPathNodesByPath[pathId];
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;
List<TaxiPathNodeRecord> pathPoints = new();
List<TaxiPathNodeRecord> pauses = new();
List<TaxiPathNodeRecord> events = new();
TransportPathLeg leg = new();
leg.MapId = path[0].ContinentID;
bool prevNodeWasTeleport = false;
uint totalTime = 0;
foreach (TaxiPathNodeRecord node in path)
{
if (node.ContinentID != leg.MapId || prevNodeWasTeleport)
{
InitializeLeg(leg, transport.Events, pathPoints, pauses, events, goInfo, ref totalTime);
leg = new();
leg.MapId = node.ContinentID;
pathPoints.Clear();
pauses.Clear();
events.Clear();
transport.PathLegs.Add(leg);
}
prevNodeWasTeleport = node.Flags.HasFlag(TaxiPathNodeFlags.Teleport);
pathPoints.Add(node);
if (node.Flags.HasFlag(TaxiPathNodeFlags.Stop))
pauses.Add(node);
if (node.ArrivalEventID != 0 || node.DepartureEventID != 0)
events.Add(node);
mapsUsed.Add(node.ContinentID);
}
if (leg.Spline == null)
InitializeLeg(leg, transport.Events, pathPoints, pauses, events, goInfo, ref totalTime);
if (mapsUsed.Count > 1)
{
foreach (uint mapId in mapsUsed)
Cypher.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable());
transport.InInstance = false;
}
else
transport.InInstance = CliDB.MapStorage.LookupByKey(mapsUsed.First()).Instanceable();
transport.TotalPathTime = totalTime;
transport.PathLegs.Add(leg);
}
public void AddPathNodeToTransport(uint transportEntry, uint timeSeg, TransportAnimationRecord node)
{
TransportAnimation animNode = new();
@@ -461,16 +427,22 @@ namespace Game.Maps
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...
Transport trans = new();
// ...at first waypoint
TaxiPathNodeRecord startNode = tInfo.keyFrames.First().Node;
uint mapId = startNode.ContinentID;
float x = startNode.Loc.X;
float y = startNode.Loc.Y;
float z = startNode.Loc.Z;
float o = tInfo.keyFrames.First().InitialOrientation;
uint mapId = tInfo.PathLegs.First().MapId;
float x = startingPosition.GetPositionX();
float y = startingPosition.GetPositionY();
float z = startingPosition.GetPositionZ();
float o = startingPosition.GetOrientation();
// initialize the gameobject base
ulong guidLow = guid != 0 ? guid : map.GenerateLowGuid(HighGuid.Transport);
@@ -482,7 +454,7 @@ namespace Game.Maps
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId);
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);
//return null;
@@ -508,7 +480,7 @@ namespace Game.Maps
uint count = 0;
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))
++count;
@@ -549,6 +521,177 @@ namespace Game.Maps
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 SplineRawInitializer(List<Vector3> points)
@@ -556,9 +699,9 @@ namespace Game.Maps
_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;
points = new Vector3[_points.Count];
@@ -572,63 +715,6 @@ namespace Game.Maps
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 Dictionary<uint, TransportAnimationRecord> Path = new();
+11 -8
View File
@@ -87,7 +87,7 @@ namespace Game.Movement
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))
{
int cyclic_point = 0;
@@ -351,7 +351,7 @@ namespace Game.Movement
#region Fields
public MoveSplineInitArgs InitArgs;
public Spline spline = new();
public Spline<int> spline = new();
public FacingInfo facing;
public MoveSplineFlag splineflags = new();
public bool onTransport;
@@ -368,7 +368,7 @@ namespace Game.Movement
public AnimTierTransition anim_tier;
#endregion
public class CommonInitializer : IInitializer
public class CommonInitializer : IInitializer<int>
{
public CommonInitializer(float _velocity)
{
@@ -377,20 +377,23 @@ namespace Game.Movement
}
public float velocityInv;
public int time;
public int SetGetTime(Spline s, int i)
public int Invoke(Spline<int> s, int i)
{
time += (int)(s.SegLength(i) * velocityInv);
return time;
}
}
public class FallInitializer : IInitializer
public class FallInitializer : IInitializer<int>
{
public FallInitializer(float startelevation)
{
startElevation = 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);
}
@@ -427,9 +430,9 @@ namespace Game.Movement
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
+29 -27
View File
@@ -23,7 +23,7 @@ using System.Numerics;
namespace Game.Movement
{
public class Spline
public class Spline<T>
{
public int GetPointCount() { return points.Length; }
public Vector3 GetPoint(int i) { return points[i]; }
@@ -262,18 +262,18 @@ namespace Game.Movement
}
#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)
{
//ASSERT(t >= 0.f && t <= 1.f);
int length_ = (int)(t * Length());
T length_ = t * (dynamic)Length();
index = ComputeIndexInBounds(length_);
//ASSERT(index < index_hi);
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
/*
@@ -294,11 +294,12 @@ namespace Game.Movement
int i = index_lo;
int N = index_hi;
while (i + 1 < N && lengths[i + 1] < length_)
while (i + 1 < N && (dynamic)lengths[i + 1] < length_)
++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_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;
}
public int Length()
public dynamic Length()
{
if (lengths.Length == 0)
return 0;
return default;
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;
Array.Resize(ref lengths, index_hi+1);
int prev_length;
int new_length;
T prev_length;
T new_length;
while (i < index_hi)
{
new_length = cacher.SetGetTime(this, i);
if (new_length < 0)
new_length = int.MaxValue;
new_length = (dynamic)cacher.Invoke(this, i);
if ((dynamic)new_length < 0)// todo fix me this is a ulgy hack.
new_length = (dynamic)(Type.GetTypeCode(typeof(T)) == TypeCode.Int32 ? int.MaxValue : double.MaxValue);
lengths[++i] = new_length;
prev_length = new_length;
@@ -354,18 +355,18 @@ namespace Game.Movement
public void InitLengths()
{
int i = index_lo;
int length = 0;
dynamic length = default(T);
Array.Resize(ref lengths, index_hi + 1);
while (i < index_hi)
{
length += (int)SegLength(i);
length += SegLength(i);
lengths[++i] = length;
}
}
public bool Empty() { return index_lo == index_hi;}
int[] lengths = Array.Empty<int>();
T[] lengths = Array.Empty<T>();
Vector3[] points = Array.Empty<Vector3>();
public EvaluationMode m_mode;
bool _cyclic;
@@ -379,14 +380,6 @@ namespace Game.Movement
int index_lo;
int index_hi;
public enum EvaluationMode
{
Linear,
Catmullrom,
Bezier3_Unused,
UninitializedMode,
ModesEnd
}
}
public class FacingInfo
@@ -396,4 +389,13 @@ namespace Game.Movement
public float angle;
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);
foreach (var point in spline.GetPoints())
@@ -425,7 +425,7 @@ namespace Game.Networking.Packets
movementSpline.SpellEffectExtraData = spellEffectExtraData;
}
Spline spline = moveSpline.spline;
var spline = moveSpline.spline;
Vector3[] array = spline.GetPoints();
if (splineFlags.HasFlag(SplineFlag.UncompressedPath))
+1 -1
View File
@@ -575,7 +575,7 @@ namespace Game.Scripting
public virtual void OnRemovePassenger(Transport transport, Player player) { }
// 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) { }
}
+2 -2
View File
@@ -863,9 +863,9 @@ namespace Game.Scripting
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