Core/Transports: Path generation rewrite v2 (needs testing)
Port From (https://github.com/TrinityCore/TrinityCore/commit/a2c1b699e6e4d30c752b65241bc23191920a86fd)
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user