Streamlines the two sprint mechanisms (the CurrentMoveSpeed guard carve-out and DoMoveImpl's follow-master 0.1 write) into one RunUO-parity model: - Order handlers own obedience speed, mirroring RunUO's OnCurrentOrderChanged and DoOrder* writes: issuing a movement order (Come/Follow/Guard/Attack) sets the active think clock, resting orders (Stay/None/Transfer) set passive, and the guard/follow peaceful branches write RunUO's AOS 0.1 sprint (guard's else-branch had the identical `if (Core.AOS) CurrentSpeed = 0.1` as follow). Pre-AOS guard returns run active. - CurrentMoveSpeed reverts to pure herding + classification — the bespoke 0.1 fuses to both clocks through the existing rule, so the sprint needs no special case and the obedience branch is deleted. - DoMoveImpl's per-step speed flip skips obeying pets (their handler owns the pace; per-step passive flips would fight it) and loses its 0.1 write. Combat still re-derives organically via warmode/combatant. Net pacing (Medium bucket): guard/follow AOS returns sprint 0.1 fused (RunUO parity, guard was previously move-clock-only), Come and friend-follow pace at activeMove (0.45, ~= the pre-#2591 feel), and the stale-Warmode active/ passive lottery is gone everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
3.2 KiB
C#
99 lines
3.2 KiB
C#
using System.Collections.Generic;
|
|
using Server;
|
|
using Server.Mobiles;
|
|
using Xunit;
|
|
|
|
namespace UOContent.Tests.Mobiles.AI;
|
|
|
|
// Guard-order following drives real movement primitives (and may pathfind), so it shares
|
|
// the pathfinding sequential collection like ApproachTargetTests.
|
|
[Collection("Sequential Pathfinding Tests")]
|
|
public class GuardFollowTests
|
|
{
|
|
[Fact]
|
|
public void GuardFollow_StepsTowardMaster_AndRegistersMoveIntent()
|
|
{
|
|
var map = Map.Maps[1];
|
|
Assert.NotNull(map);
|
|
map.GetAverageZ(1500, 1600, out _, out var z, out _);
|
|
|
|
var master = new PlayerMobile(World.NewMobile);
|
|
master.DefaultMobileInit();
|
|
master.MoveToWorld(new Point3D(1494, 1600, (sbyte)z), map);
|
|
|
|
var pet = new PetTestStub();
|
|
pet.MoveToWorld(new Point3D(1500, 1600, (sbyte)z), map); // 6 tiles east, open terrain
|
|
pet.SetControlMaster(master);
|
|
|
|
var ai = pet.AIObject;
|
|
ai.AITimer?.Stop(); // drive manually
|
|
pet.ControlOrder = OrderType.Guard;
|
|
ai.AITimer?.Stop(); // the order change may restart the timer
|
|
|
|
var start = pet.Location;
|
|
ai.NextMove = 0;
|
|
ai.Obey();
|
|
|
|
var moved = pet.Location != start;
|
|
var hasIntent = ai.TryGetMoveWake(out _);
|
|
var currentSpeed = pet.CurrentSpeed;
|
|
var currentMoveSpeed = pet.CurrentMoveSpeed;
|
|
|
|
pet.Delete();
|
|
master.Delete();
|
|
|
|
Assert.True(moved, "a guarding pet beyond guard range must step toward its master");
|
|
// Between-think move wakes require a registered move intent; bare greedy stepping
|
|
// quantizes guard-following to the think grid (issue #2593).
|
|
Assert.True(hasIntent, "guard-following must register a move intent");
|
|
|
|
// RunUO AOS parity: the guard return sprints at the bespoke 0.1, fused to both
|
|
// clocks, and the per-step speed flip must not undo it (fixture era is EJ).
|
|
Assert.Equal(0.1, currentSpeed);
|
|
Assert.Equal(0.1, currentMoveSpeed);
|
|
}
|
|
|
|
[Fact]
|
|
public void GuardReturn_PreAOS_RunsActive()
|
|
{
|
|
var previous = Core.Expansion;
|
|
|
|
try
|
|
{
|
|
Core.Expansion = Expansion.UOR;
|
|
|
|
var map = Map.Maps[1];
|
|
Assert.NotNull(map);
|
|
map.GetAverageZ(1500, 1600, out _, out var z, out _);
|
|
|
|
var master = new PlayerMobile(World.NewMobile);
|
|
master.DefaultMobileInit();
|
|
master.MoveToWorld(new Point3D(1494, 1600, (sbyte)z), map);
|
|
|
|
var pet = new PetTestStub();
|
|
pet.MoveToWorld(new Point3D(1500, 1600, (sbyte)z), map);
|
|
pet.SetControlMaster(master);
|
|
|
|
var ai = pet.AIObject;
|
|
ai.AITimer?.Stop();
|
|
pet.ControlOrder = OrderType.Guard;
|
|
ai.AITimer?.Stop();
|
|
pet.SetCurrentSpeedToPassive(); // a stale passive state must not persist
|
|
|
|
ai.NextMove = 0;
|
|
ai.Obey();
|
|
|
|
var currentSpeed = pet.CurrentSpeed;
|
|
|
|
pet.Delete();
|
|
master.Delete();
|
|
|
|
// No sprint pre-AOS: the return runs organically active.
|
|
Assert.Equal(0.2, currentSpeed);
|
|
}
|
|
finally
|
|
{
|
|
Core.Expansion = previous;
|
|
}
|
|
}
|
|
}
|