feat: herding paces the movement clock, not the think clock
Herding is a movement scenario: the old CurrentSpeed getter hack forced 0.3 on the fused clock, which under the split misclassified - a herded mob whose active think happens to be 0.3 walked at its (slow) move value while any other mob got a bespoke 0.3. Now CurrentMoveSpeed drives a herded creature at a fixed 0.3s per step - RunUO's forced pace, without its TransformMoveDelay inflation to 0.6 - so herding is never penalized by a slow creature. Thinking is untouched. CheckHerding walks through MoveToPoint (cached boxed goal) instead of a greedy per-think DoMove, so the movement clock actually governs pace between think ticks - and herded creatures path around obstacles instead of walking into them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
41ae2d8ffe
commit
5429465d64
3 changed files with 41 additions and 19 deletions
|
|
@ -124,6 +124,21 @@ public class MoveSpeedTests : IDisposable
|
|||
Assert.Equal(bc.PassiveSpeed, bc.PassiveMoveSpeed); // still inheriting, not 0 * scalar
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Herding_DrivesMoveClock_ThinkUntouched()
|
||||
{
|
||||
var bc = NewCreature(); // think 0.3/0.6, passive
|
||||
bc.SetMoveSpeed(0.45, 1.05);
|
||||
|
||||
bc.TargetLocation = new Point2D(10, 10);
|
||||
|
||||
Assert.Equal(0.6, bc.CurrentSpeed); // think clock unaffected by herding
|
||||
Assert.Equal(0.3, bc.CurrentMoveSpeed); // fixed herding pace, not 1.05
|
||||
|
||||
bc.TargetLocation = null;
|
||||
Assert.Equal(1.05, bc.CurrentMoveSpeed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SnapSpeedsToTable_UndoesScalingDrift_KeepsTunedValues()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public abstract partial class BaseAI
|
|||
private Mobile _lkpTarget;
|
||||
private Point3D _lkpLocation;
|
||||
private IPoint3D _lkpGoal; // boxed _lkpLocation handed to the PathFollower
|
||||
private IPoint3D _herdGoal; // boxed herding goal handed to the PathFollower
|
||||
private long _lkpExpireTick;
|
||||
private long _guardStopTick;
|
||||
private long _investigateStopTick;
|
||||
|
|
@ -627,6 +628,7 @@ public abstract partial class BaseAI
|
|||
|
||||
if (target == null)
|
||||
{
|
||||
_herdGoal = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -634,7 +636,15 @@ public abstract partial class BaseAI
|
|||
|
||||
if (distance >= 1 && distance <= 15)
|
||||
{
|
||||
DoMove(Mobile.GetDirectionTo(target));
|
||||
// A cached boxed goal keeps the PathFollower persistent across ticks; walking
|
||||
// through MoveToPoint paces herding on the movement clock and paths around
|
||||
// obstacles.
|
||||
if (_herdGoal == null || _herdGoal.X != target.X || _herdGoal.Y != target.Y)
|
||||
{
|
||||
_herdGoal = new Point3D(target.X, target.Y, Mobile.Map?.GetAverageZ(target.X, target.Y) ?? Mobile.Z);
|
||||
}
|
||||
|
||||
MoveToPoint(_herdGoal, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -644,6 +654,7 @@ public abstract partial class BaseAI
|
|||
}
|
||||
|
||||
Mobile.TargetLocation = null;
|
||||
_herdGoal = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,8 +269,8 @@ namespace Server.Mobiles
|
|||
private double _activeMoveSpeed;
|
||||
private double _passiveMoveSpeed;
|
||||
|
||||
// Herding - Overrides the AI to force the mob to move to a specific location
|
||||
// Thinking: 0.3s, Movement: 0.6s.
|
||||
// Herding - forces the mob to walk to a specific location, paced by the movement
|
||||
// clock at HerdingMoveSpeed. Thinking is unaffected.
|
||||
private IPoint2D _targetLocation;
|
||||
|
||||
private int m_DamageMax = -1;
|
||||
|
|
@ -723,21 +723,21 @@ namespace Server.Mobiles
|
|||
set => _passiveMoveSpeed = value > 0 ? value : 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public IPoint2D TargetLocation
|
||||
{
|
||||
get => _targetLocation;
|
||||
set
|
||||
{
|
||||
_targetLocation = value;
|
||||
AIObject?.OnCurrentSpeedChanged();
|
||||
}
|
||||
set => _targetLocation = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public double CurrentSpeed
|
||||
{
|
||||
get => _targetLocation != null ? 0.3 : _currentSpeed;
|
||||
get => _currentSpeed;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(_currentSpeed - value) > 0.0001)
|
||||
|
|
@ -751,25 +751,21 @@ namespace Server.Mobiles
|
|||
/// <summary>
|
||||
/// Resolved seconds per step: a verbatim active/passive <see cref="CurrentSpeed"/>
|
||||
/// maps to the matching movement value; a bespoke pace stays fused to both clocks.
|
||||
/// A herded creature is always driven at <see cref="HerdingMoveSpeed"/>.
|
||||
/// </summary>
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public double CurrentMoveSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
var current = CurrentSpeed;
|
||||
|
||||
if (current == _activeSpeed)
|
||||
if (_targetLocation != null)
|
||||
{
|
||||
return ActiveMoveSpeed;
|
||||
return HerdingMoveSpeed;
|
||||
}
|
||||
|
||||
if (current == _passiveSpeed)
|
||||
{
|
||||
return PassiveMoveSpeed;
|
||||
}
|
||||
|
||||
return current;
|
||||
return _currentSpeed == _activeSpeed ? ActiveMoveSpeed
|
||||
: _currentSpeed == _passiveSpeed ? PassiveMoveSpeed
|
||||
: _currentSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue