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 <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-08-23 09:36:54 -07:00
parent 6c9d603ae4
commit 5bf9fbefb6
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
2 changed files with 55 additions and 1 deletions

View file

@ -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)]

View file

@ -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;