diff --git a/Source/Framework/Constants/GameObjectConst.cs b/Source/Framework/Constants/GameObjectConst.cs index b873107af..5913b0564 100644 --- a/Source/Framework/Constants/GameObjectConst.cs +++ b/Source/Framework/Constants/GameObjectConst.cs @@ -200,4 +200,10 @@ namespace Framework.Constants SetTappedToChallengePlayers = 44, // Set Tapped to Challenge Players Max } + + public enum TransportMovementState + { + Moving, + WaitingOnPauseWaypoint + } } diff --git a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs index e23c13914..bae5ea327 100644 --- a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs +++ b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs @@ -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 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 _polygonVertices; - Spline _spline; + Spline _spline; bool _reachedDestination; int _lastSplineIndex; diff --git a/Source/Game/Entities/Object/Update/UpdateFields.cs b/Source/Game/Entities/Object/Update/UpdateFields.cs index 0ec1a9a35..2c43856c9 100644 --- a/Source/Game/Entities/Object/Update/UpdateFields.cs +++ b/Source/Game/Entities/Object/Update/UpdateFields.cs @@ -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; } } diff --git a/Source/Game/Entities/Transport.cs b/Source/Game/Entities/Transport.cs index 23715a82b..ca8972bc2 100644 --- a/Source/Game/Entities/Transport.cs +++ b/Source/Game/Entities/Transport.cs @@ -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 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 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 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 _passengers = new(); HashSet _staticPassengers = new(); bool _delayedAddModel; - bool _delayedTeleport; + WorldLocation _delayedTeleport; } } diff --git a/Source/Game/Maps/TransportManager.cs b/Source/Game/Maps/TransportManager.cs index 673617bcf..a2e415e37 100644 --- a/Source/Game/Maps/TransportManager.cs +++ b/Source/Game/Maps/TransportManager.cs @@ -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 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 outEvents, List pathPoints, List pauses, List events, GameObjectTemplate goInfo, ref uint totalTime) { - uint pathId = goInfo.MoTransport.taxiPathID; - var path = CliDB.TaxiPathNodesByPath[pathId]; - List keyFrames = transport.keyFrames; - List splinePath = new(); - List allPoints = new(); - bool mapChange = false; + List splinePath = new(pathPoints.Select(node => new Vector3(node.Loc.X, node.Loc.Y, node.Loc.Z))); + SplineRawInitializer initer = new(splinePath); + leg.Spline = new Spline(); + 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 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 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 pathPoints = new(); + List pauses = new(); + List 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 _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 Spline; + public uint StartTimestamp; + public uint Duration; + public List Segments = new(); + } + + public class TransportTemplate + { + public uint TotalPathTime; + public double Speed; + public double AccelerationRate; + public double AccelerationTime; + public double AccelerationDistance; + public List PathLegs = new(); + public List 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 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 _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 mapsUsed = new(); - public bool inInstance; - public uint pathTime; - public List keyFrames = new(); - public float accelTime; - public float accelDist; - public uint entry; - } - public class TransportAnimation { public Dictionary Path = new(); diff --git a/Source/Game/Movement/MoveSpline.cs b/Source/Game/Movement/MoveSpline.cs index 8e74d4106..7a7e4c13b 100644 --- a/Source/Game/Movement/MoveSpline.cs +++ b/Source/Game/Movement/MoveSpline.cs @@ -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 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 { 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 s, int i) { time += (int)(s.SegLength(i) * velocityInv); return time; } } - public class FallInitializer : IInitializer + + public class FallInitializer : IInitializer { public FallInitializer(float startelevation) { startElevation = startelevation; } float startElevation; - public int SetGetTime(Spline s, int i) + + public int Invoke(Spline 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 { - int SetGetTime(Spline s, int i); + int Invoke(Spline s, int i); } public class SplineChainLink diff --git a/Source/Game/Movement/Spline.cs b/Source/Game/Movement/Spline.cs index 8f22e6eae..8c3e08f9d 100644 --- a/Source/Game/Movement/Spline.cs +++ b/Source/Game/Movement/Spline.cs @@ -23,7 +23,7 @@ using System.Numerics; namespace Game.Movement { - public class Spline + public class Spline { 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 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(); + T[] lengths = Array.Empty(); Vector3[] points = Array.Empty(); 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 + } } diff --git a/Source/Game/Networking/Packets/MovementPackets.cs b/Source/Game/Networking/Packets/MovementPackets.cs index af712980c..af17f65e9 100644 --- a/Source/Game/Networking/Packets/MovementPackets.cs +++ b/Source/Game/Networking/Packets/MovementPackets.cs @@ -301,7 +301,7 @@ namespace Game.Networking.Packets } } - public static void WriteCreateObjectAreaTriggerSpline(Spline spline, WorldPacket data) + public static void WriteCreateObjectAreaTriggerSpline(Spline 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)) diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index 737a09167..262e9edb9 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -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) { } } diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 629c48194..e82073ed9 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -863,9 +863,9 @@ namespace Game.Scripting RunScript(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(p => p.OnRelocate(transport, waypointId, mapId, x, y, z), transport.GetScriptId()); + RunScript(p => p.OnRelocate(transport, mapId, x, y, z), transport.GetScriptId()); } // Achievement