diff --git a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs index 5fd594ad8..f22383cf3 100644 --- a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs +++ b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs @@ -124,6 +124,29 @@ public class MoveSpeedTests : IDisposable Assert.Equal(bc.PassiveSpeed, bc.PassiveMoveSpeed); // still inheriting, not 0 * scalar } + [Fact] + public void SnapSpeedsToTable_UndoesScalingDrift_KeepsTunedValues() + { + var bc = NewCreature(); + bc.TableActiveMove = 0.45; + bc.TablePassiveMove = 0.9; + bc.SetMoveSpeed(0.45, 0.9); + + // 0.45 and 0.9 do not survive /1.2 then *1.2 bit-exactly. + bc.ScaleMoveSpeed(1.0 / 1.2); + bc.ScaleMoveSpeed(1.2); + Assert.NotEqual(0.45, bc.ActiveMoveSpeed); + + bc.SnapSpeedsToTable(); + Assert.Equal(0.45, bc.ActiveMoveSpeed); + Assert.Equal(0.9, bc.PassiveMoveSpeed); + + // A hand-tuned value is nowhere near the epsilon and must keep. + bc.SetMoveSpeed(0.7, 0.9); + bc.SnapSpeedsToTable(); + Assert.Equal(0.7, bc.ActiveMoveSpeed); + } + [Fact] public void Migration_MatchingThinkSpeeds_AdoptTableMoveValues() { diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 5e001fdbf..016fcaa27 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -773,9 +773,6 @@ namespace Server.Mobiles } } - [CommandProperty(AccessLevel.GameMaster)] - public double MoveSpeedMod { get; set; } - [CommandProperty(AccessLevel.GameMaster)] public Point3D Home { @@ -4691,6 +4688,35 @@ namespace Server.Mobiles } } + /// + /// Snaps speeds within rounding distance of the creature's table values back to + /// exact. A scaling buff that divides then multiplies can drift by an ulp (e.g. + /// 0.9 and 0.45 through 1.2), which would read as hand-tuned; call after undoing + /// such a buff. Genuinely tuned speeds are nowhere near the epsilon and keep. + /// + public void SnapSpeedsToTable() + { + GetSpeeds(out var activeSpeed, out var passiveSpeed); + + if (Math.Abs(_activeSpeed - activeSpeed) < 0.0001 && Math.Abs(_passiveSpeed - passiveSpeed) < 0.0001) + { + _activeSpeed = activeSpeed; + _passiveSpeed = passiveSpeed; + } + + GetMoveSpeeds(out var activeMoveSpeed, out var passiveMoveSpeed); + + if (activeMoveSpeed > 0 && Math.Abs(_activeMoveSpeed - activeMoveSpeed) < 0.0001) + { + _activeMoveSpeed = activeMoveSpeed; + } + + if (passiveMoveSpeed > 0 && Math.Abs(_passiveMoveSpeed - passiveMoveSpeed) < 0.0001) + { + _passiveMoveSpeed = passiveMoveSpeed; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetCurrentSpeedToActive() => CurrentSpeed = ActiveSpeed; diff --git a/Projects/UOContent/Mobiles/Special/Paragon.cs b/Projects/UOContent/Mobiles/Special/Paragon.cs index 12a21882e..cbd0a30c7 100644 --- a/Projects/UOContent/Mobiles/Special/Paragon.cs +++ b/Projects/UOContent/Mobiles/Special/Paragon.cs @@ -145,6 +145,7 @@ public static class Paragon bc.PassiveSpeed *= SpeedBuff; bc.ActiveSpeed *= SpeedBuff; bc.ScaleMoveSpeed(SpeedBuff); + bc.SnapSpeedsToTable(); // an ulp of scaling drift must not read as hand-tuned bc.CurrentSpeed = bc.PassiveSpeed; bc.DamageMin -= DamageBuff;