diff --git a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs
index 7d9243a29..7ad149eba 100644
--- a/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs
+++ b/Projects/UOContent.Tests/Tests/Mobiles/AI/MoveSpeedTests.cs
@@ -57,8 +57,9 @@ public class MoveSpeedTests : IDisposable
{
var bc = NewCreature();
- Assert.Equal(0.3, bc.ActiveMoveSpeed);
- Assert.Equal(0.6, bc.PassiveMoveSpeed);
+ // 0 = no override; the resolved pace comes from CurrentMoveSpeed.
+ Assert.Equal(0, bc.ActiveMoveSpeed);
+ Assert.Equal(0, bc.PassiveMoveSpeed);
Assert.Equal(bc.CurrentSpeed, bc.CurrentMoveSpeed);
}
@@ -96,8 +97,8 @@ public class MoveSpeedTests : IDisposable
bc.SetSpeed(0.2, 0.4);
- Assert.Equal(0.2, bc.ActiveMoveSpeed);
- Assert.Equal(0.4, bc.PassiveMoveSpeed);
+ Assert.Equal(0, bc.ActiveMoveSpeed);
+ Assert.Equal(0, bc.PassiveMoveSpeed);
}
[Fact]
@@ -108,8 +109,10 @@ public class MoveSpeedTests : IDisposable
bc.ActiveMoveSpeed = 0;
- Assert.Equal(0.3, bc.ActiveMoveSpeed); // inheriting again
+ Assert.Equal(0, bc.ActiveMoveSpeed); // inheriting again
Assert.Equal(0.9, bc.PassiveMoveSpeed); // other override untouched
+ bc.SetCurrentSpeedToActive();
+ Assert.Equal(0.3, bc.CurrentMoveSpeed); // resolves to the think clock
}
[Fact]
@@ -121,7 +124,7 @@ public class MoveSpeedTests : IDisposable
bc.ScaleMoveSpeed(1.0 / 1.2);
Assert.Equal(0.5, bc.ActiveMoveSpeed);
- Assert.Equal(bc.PassiveSpeed, bc.PassiveMoveSpeed); // still inheriting, not 0 * scalar
+ Assert.Equal(0, bc.PassiveMoveSpeed); // still inheriting, not 0 * scalar
}
[Fact]
@@ -185,8 +188,8 @@ public class MoveSpeedTests : IDisposable
bc.MigrateMoveSpeeds();
- Assert.Equal(0.35, bc.ActiveMoveSpeed);
- Assert.Equal(0.6, bc.PassiveMoveSpeed);
+ Assert.Equal(0, bc.ActiveMoveSpeed); // still inheriting the (tuned) think clock
+ Assert.Equal(0, bc.PassiveMoveSpeed);
}
[Theory]
@@ -213,7 +216,7 @@ public class MoveSpeedTests : IDisposable
// The v22 tail is the last block; exact consumption catches any offset mistake.
Assert.Equal(buffer.Length, reader.Position);
- Assert.Equal(overridden ? 0.45 : 0.3, copy.ActiveMoveSpeed);
- Assert.Equal(overridden ? 0.9 : 0.6, copy.PassiveMoveSpeed);
+ Assert.Equal(overridden ? 0.45 : 0, copy.ActiveMoveSpeed);
+ Assert.Equal(overridden ? 0.9 : 0, copy.PassiveMoveSpeed);
}
}
diff --git a/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs
index 7425cdab7..6a60f12d3 100644
--- a/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs
+++ b/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs
@@ -269,7 +269,8 @@ public class BaseCreatureSerializationTests : IDisposable
Assert.Equal(10, copy.FireResistSeed);
Assert.Equal(0.3, copy.ActiveSpeed);
// v22 wrote explicit zeros for the move overrides ("inherit"), so the resolved
- // pace falls back to the think clock through the resolving getters.
- Assert.Equal(0.3, copy.ActiveMoveSpeed);
+ // pace falls back to the think clock.
+ Assert.Equal(0, copy.ActiveMoveSpeed);
+ Assert.Equal(0.6, copy.CurrentMoveSpeed); // passive mode, inheriting
}
}
diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs
index 3953916fc..88a03a8a7 100644
--- a/Projects/UOContent/Mobiles/BaseCreature.cs
+++ b/Projects/UOContent/Mobiles/BaseCreature.cs
@@ -363,11 +363,30 @@ namespace Server.Mobiles
private void OnCurrentSpeedChange(double oldValue, double newValue) => AIObject?.OnCurrentSpeedChanged();
- // Movement clock (seconds per step); 0 = inherit the matching think value.
- // Serialized through the hand-written resolving properties (fields 10 and 11).
+ ///
+ /// Movement clock (seconds per step) while engaged; 0 = inherit
+ /// . resolves the pace.
+ ///
+ [SerializableField(10, allowFieldChange: nameof(CoerceMoveSpeed))]
+ [SaveFlag(nameof(ShouldSerializeActiveMoveSpeed), nameof(ActiveMoveSpeedDefaultValue))]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
private double _activeMoveSpeed;
+
+ ///
+ /// Movement clock (seconds per step) while idle; 0 = inherit
+ /// . resolves the pace.
+ ///
+ [SerializableField(11, allowFieldChange: nameof(CoerceMoveSpeed))]
+ [SaveFlag(nameof(ShouldSerializePassiveMoveSpeed), nameof(PassiveMoveSpeedDefaultValue))]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
private double _passiveMoveSpeed;
+ private bool CoerceMoveSpeed(ref double value)
+ {
+ value = Math.Max(0, value); // anything non-positive means "inherit"
+ return true;
+ }
+
private bool ShouldSerializeActiveMoveSpeed()
{
GetMoveSpeeds(out var activeMoveSpeed, out _);
@@ -1075,34 +1094,6 @@ namespace Server.Mobiles
[CommandProperty(AccessLevel.GameMaster)]
public virtual int ChaseLeashRange => RangePerception * 2;
- /// Seconds per step while engaged. Inherits ; set 0 to re-inherit.
- [SerializableProperty(10, useField: nameof(_activeMoveSpeed))]
- [SaveFlag(nameof(ShouldSerializeActiveMoveSpeed), nameof(ActiveMoveSpeedDefaultValue))]
- [CommandProperty(AccessLevel.GameMaster)]
- public virtual double ActiveMoveSpeed
- {
- get => _activeMoveSpeed > 0 ? _activeMoveSpeed : _activeSpeed;
- set
- {
- _activeMoveSpeed = value > 0 ? value : 0;
- this.MarkDirty();
- }
- }
-
- /// Seconds per step while idle. Inherits ; set 0 to re-inherit.
- [SerializableProperty(11, useField: nameof(_passiveMoveSpeed))]
- [SaveFlag(nameof(ShouldSerializePassiveMoveSpeed), nameof(PassiveMoveSpeedDefaultValue))]
- [CommandProperty(AccessLevel.GameMaster)]
- public virtual double PassiveMoveSpeed
- {
- get => _passiveMoveSpeed > 0 ? _passiveMoveSpeed : _passiveSpeed;
- set
- {
- _passiveMoveSpeed = value > 0 ? value : 0;
- this.MarkDirty();
- }
- }
-
// Herded creatures walk at a fixed standard pace regardless of their own speed
// (RunUO's forced 0.3, without its TransformMoveDelay inflation to 0.6).
private const double HerdingMoveSpeed = 0.3;
@@ -1129,9 +1120,17 @@ namespace Server.Mobiles
return HerdingMoveSpeed;
}
- return _currentSpeed == _activeSpeed ? ActiveMoveSpeed
- : _currentSpeed == _passiveSpeed ? PassiveMoveSpeed
- : _currentSpeed;
+ if (_currentSpeed == _activeSpeed)
+ {
+ return _activeMoveSpeed > 0 ? _activeMoveSpeed : _activeSpeed;
+ }
+
+ if (_currentSpeed == _passiveSpeed)
+ {
+ return _passiveMoveSpeed > 0 ? _passiveMoveSpeed : _passiveSpeed;
+ }
+
+ return _currentSpeed;
}
}
diff --git a/dev-docs/content-patterns.md b/dev-docs/content-patterns.md
index ec56e1bb3..2c78fcd67 100644
--- a/dev-docs/content-patterns.md
+++ b/dev-docs/content-patterns.md
@@ -262,8 +262,9 @@ All "speed" values are **delays in seconds** (smaller = faster). A creature runs
(combat decisions, target acquisition, spell timing).
- **Move clock** — `ActiveMoveSpeed`/`PassiveMoveSpeed`/`CurrentMoveSpeed`: seconds per
step. Inherits the matching think value until overridden, so a creature configured with
- only think speeds behaves as one clock. Any value is legal — steps are scheduled
- independently of think ticks, so the two need not divide evenly.
+ only think speeds behaves as one clock. The properties read the raw override (`0` =
+ inheriting); `CurrentMoveSpeed` is the resolved pace. Any value is legal — steps are
+ scheduled independently of think ticks, so the two need not divide evenly.
Speeds normally come from `Distribution/Data/npc-speeds.json` (via `SpeedClass` or type
lists); `activeMove`/`passiveMove` are optional per bucket. Prefer data over code: