feat(headless): complete deterministic bot command parity

This commit is contained in:
Erik 2026-07-27 08:23:36 +02:00
parent 7e8acb74dd
commit 38e83640d9
37 changed files with 2805 additions and 295 deletions

View file

@ -67,6 +67,12 @@ public sealed class DirectGameRuntimeCommandAdapterTests
var gameActions = new List<byte[]>();
operations.Sessions[^1].GameActionCapture =
body => gameActions.Add(body);
const uint selectedObject = 0x70000001u;
runtime.InventoryOwner.Objects.AddOrUpdate(new ClientObject
{
ObjectId = selectedObject,
Type = ItemType.Misc,
});
RuntimeCommandResult chat = adapter.Chat.Execute(
runtime.Generation,
@ -76,9 +82,128 @@ public sealed class DirectGameRuntimeCommandAdapterTests
RuntimeCommandResult portal = adapter.Portal.Execute(
runtime.Generation,
RuntimePortalCommand.RecallLifestone);
RuntimeCommandResult unsupported = adapter.Movement.Execute(
runtime.Generation,
RuntimeMovementCommand.Stop);
runtime.CommunicationOwner.TurbineChat.OnChannelsReceived(
allegianceRoom: 0x10u,
generalRoom: 0x11u,
tradeRoom: 0x12u,
lfgRoom: 0x13u,
roleplayRoom: 0x14u,
olthoiRoom: 0x15u,
societyRoom: 0x16u,
societyCelestialHandRoom: 0u,
societyEldrytchWebRoom: 0u,
societyRadiantBloodRoom: 0u);
RuntimeCommandResult[] stateAndWireCommands =
[
adapter.Selection.SelectObject(
runtime.Generation,
selectedObject),
adapter.Selection.Clear(runtime.Generation),
adapter.Movement.SetIntent(
runtime.Generation,
new MovementInput(Forward: true, Run: true)),
adapter.Movement.ClearIntent(runtime.Generation),
adapter.Movement.Execute(
runtime.Generation,
RuntimeMovementCommand.Stop),
adapter.Chat.Execute(
runtime.Generation,
new RuntimeChatCommand(
RuntimeChatChannel.Fellowship,
"group")),
adapter.Chat.Execute(
runtime.Generation,
new RuntimeChatCommand(
RuntimeChatChannel.General,
"global")),
adapter.InventoryState.AddShortcut(
runtime.Generation,
new RuntimeShortcutCommand(0, 0x70000001u, 0u)),
adapter.InventoryState.RemoveShortcut(
runtime.Generation,
0),
adapter.Spellbook.AddFavorite(
runtime.Generation,
tabIndex: 0,
position: 0,
spellId: 7u),
adapter.Spellbook.RemoveFavorite(
runtime.Generation,
tabIndex: 0,
spellId: 7u),
adapter.Spellbook.SetFilter(
runtime.Generation,
filters: 3u),
adapter.Spellbook.ForgetSpell(
runtime.Generation,
spellId: 7u),
adapter.Spellbook.SetDesiredComponent(
runtime.Generation,
componentId: 11u,
amount: 3u),
adapter.Spellbook.ClearDesiredComponents(
runtime.Generation),
adapter.Character.Advance(
runtime.Generation,
new RuntimeAdvancementCommand(
RuntimeAdvancementKind.Attribute,
StatId: 1u,
Cost: 10u)),
adapter.Character.Advance(
runtime.Generation,
new RuntimeAdvancementCommand(
RuntimeAdvancementKind.Vital,
StatId: 2u,
Cost: 11u)),
adapter.Character.Advance(
runtime.Generation,
new RuntimeAdvancementCommand(
RuntimeAdvancementKind.Skill,
StatId: 3u,
Cost: 12u)),
adapter.Character.Advance(
runtime.Generation,
new RuntimeAdvancementCommand(
RuntimeAdvancementKind.TrainSkill,
StatId: 4u,
Cost: 1u)),
adapter.Character.SetOptions1(
runtime.Generation,
options: 0x1234u),
adapter.Social.Execute(
runtime.Generation,
new RuntimeFriendCommand(
RuntimeFriendCommandKind.Add,
Name: "Friend")),
adapter.Social.Execute(
runtime.Generation,
new RuntimeFriendCommand(
RuntimeFriendCommandKind.Remove,
CharacterId: 0x50000003u)),
adapter.Social.Execute(
runtime.Generation,
new RuntimeFriendCommand(
RuntimeFriendCommandKind.Clear)),
adapter.Social.Execute(
runtime.Generation,
new RuntimeSquelchCommand(
RuntimeSquelchScope.Character,
Add: true,
CharacterId: 0x50000004u,
Name: "Muted")),
adapter.Social.Execute(
runtime.Generation,
new RuntimeSquelchCommand(
RuntimeSquelchScope.Account,
Add: true,
Name: "AccountMuted")),
adapter.Social.Execute(
runtime.Generation,
new RuntimeSquelchCommand(
RuntimeSquelchScope.Global,
Add: true,
MessageType: 2u)),
];
RuntimeSessionStartResult reconnected =
adapter.Session.Reconnect(runtime.Generation);
@ -87,6 +212,10 @@ public sealed class DirectGameRuntimeCommandAdapterTests
new RuntimeChatCommand(
RuntimeChatChannel.Say,
"stale"));
RuntimeCommandResult staleMovement =
adapter.Movement.SetIntent(
firstGeneration,
new MovementInput(Forward: true));
Assert.Equal(RuntimeSessionStartStatus.Connected, started.Status);
Assert.Equal(
@ -94,13 +223,19 @@ public sealed class DirectGameRuntimeCommandAdapterTests
reconnected.Status);
Assert.True(chat.Accepted);
Assert.True(portal.Accepted);
Assert.Equal(
RuntimeCommandStatus.Unsupported,
unsupported.Status);
Assert.All(
stateAndWireCommands,
result => Assert.Equal(
RuntimeCommandStatus.Accepted,
result.Status));
Assert.Equal(
RuntimeCommandStatus.StaleGeneration,
stale.Status);
Assert.Equal(2, gameActions.Count);
Assert.Equal(
RuntimeCommandStatus.StaleGeneration,
staleMovement.Status);
Assert.False(runtime.MovementOwner.HasCommandInput);
Assert.True(gameActions.Count >= 20);
Assert.Contains(
trace.Entries,
entry => entry.Kind == RuntimeTraceKind.Command
@ -112,6 +247,9 @@ public sealed class DirectGameRuntimeCommandAdapterTests
entry => entry.Kind == RuntimeTraceKind.Command
&& (entry.Code >> 16)
== (int)RuntimeCommandDomain.Portal);
Assert.Contains(
trace.Entries,
entry => entry.Kind == RuntimeTraceKind.Combat);
RuntimeTeardownAcknowledgement stopped =
adapter.Session.Stop(runtime.Generation);