From 5bf9fbefb6e1e69618791d27b11da98fa03534c5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:36:54 -0700 Subject: [PATCH] feat: seed move speeds for pre-v22 creatures on load A creature deserialized from a pre-v22 save whose think speeds still match what it would spawn with today was never hand-tuned: adopt today's table move values, so existing worlds and pets pick up npc-speeds pacing without a respawn. A creature with tuned think speeds no longer matches and keeps movement inheriting its think clock. Co-Authored-By: Claude Fable 5 --- .../Tests/Mobiles/AI/MoveSpeedTests.cs | 38 ++++++++++++++++++- Projects/UOContent/Mobiles/BaseCreature.cs | 18 +++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs index b7c852003..bea4e5396 100644 --- a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs +++ b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs @@ -24,16 +24,25 @@ public class MoveSpeedTests : IDisposable private sealed class SpeedStub : BaseCreature { + // Stands in for the npc-speeds table (unconfigured in the test fixture). + public double TableActiveMove; + public double TablePassiveMove; + public SpeedStub() : base(AIType.AI_Animal) => Body = 0xC9; public SpeedStub(Serial serial) : base(serial) => Body = 0xC9; - // NPCSpeeds isn't configured in the test fixture; provide fixed think speeds. public override void GetSpeeds(out double activeSpeed, out double passiveSpeed) { activeSpeed = 0.3; passiveSpeed = 0.6; } + + public override void GetMoveSpeeds(out double activeMoveSpeed, out double passiveMoveSpeed) + { + activeMoveSpeed = TableActiveMove; + passiveMoveSpeed = TablePassiveMove; + } } private SpeedStub NewCreature() @@ -103,6 +112,33 @@ public class MoveSpeedTests : IDisposable Assert.Equal(0.9, bc.PassiveMoveSpeed); // other override untouched } + [Fact] + public void Migration_MatchingThinkSpeeds_AdoptTableMoveValues() + { + var bc = NewCreature(); // think 0.3/0.6, matching its table entry + bc.TableActiveMove = 0.45; + bc.TablePassiveMove = 0.9; + + bc.MigrateMoveSpeeds(); + + Assert.Equal(0.45, bc.ActiveMoveSpeed); + Assert.Equal(0.9, bc.PassiveMoveSpeed); + } + + [Fact] + public void Migration_TunedThinkSpeeds_KeepInheriting() + { + var bc = NewCreature(); + bc.SetSpeed(0.35, 0.6); // hand-tuned: no longer matches the table entry + bc.TableActiveMove = 0.45; + bc.TablePassiveMove = 0.9; + + bc.MigrateMoveSpeeds(); + + Assert.Equal(0.35, bc.ActiveMoveSpeed); + Assert.Equal(0.6, bc.PassiveMoveSpeed); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 6194241dd..4ca1b9ae2 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -2230,6 +2230,10 @@ namespace Server.Mobiles _activeMoveSpeed = reader.ReadDouble(); _passiveMoveSpeed = reader.ReadDouble(); } + else + { + MigrateMoveSpeeds(); + } if (version <= 14 && m_Paragon && Hue == 0x31) { @@ -4988,6 +4992,20 @@ namespace Server.Mobiles NPCSpeeds.GetMoveSpeeds(this, out activeMoveSpeed, out passiveMoveSpeed); } + // Pre-v22 saves carry no movement clock. A creature whose serialized think speeds + // still match what it would spawn with today was never hand-tuned: adopt today's + // move values so existing worlds (and pets) pick up npc-speeds pacing without a + // respawn. Tuned creatures keep movement inheriting their think clock. + internal void MigrateMoveSpeeds() + { + GetSpeeds(out var activeSpeed, out var passiveSpeed); + + if (_activeSpeed == activeSpeed && _passiveSpeed == passiveSpeed) + { + GetMoveSpeeds(out _activeMoveSpeed, out _passiveMoveSpeed); + } + } + public virtual void DropBackpack() { var backpack = Backpack;