feat(headless): complete deterministic bot command parity
This commit is contained in:
parent
7e8acb74dd
commit
38e83640d9
37 changed files with 2805 additions and 295 deletions
|
|
@ -255,6 +255,7 @@ public sealed class GameRuntimeTests
|
|||
public void OnChat(in RuntimeChatDelta delta) => Chat.Add(delta);
|
||||
public void OnMovement(in RuntimeMovementDelta delta) { }
|
||||
public void OnPortal(in RuntimePortalDelta delta) { }
|
||||
public void OnCombat(in RuntimeCombatDelta delta) { }
|
||||
}
|
||||
|
||||
private sealed class InjectedConstructionFailure : Exception
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
public sealed class RuntimeHostileTargetQueryTests
|
||||
{
|
||||
private const uint Player = 0x50000001u;
|
||||
|
||||
[Fact]
|
||||
public void FindClosest_UsesAbsoluteDerethCoordinatesAcrossLandblocks()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
Add(
|
||||
runtime,
|
||||
Player,
|
||||
landblock: 0x01010001u,
|
||||
x: 191f,
|
||||
y: 100f,
|
||||
PlayerObject(Player));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000010u,
|
||||
landblock: 0x02010001u,
|
||||
x: 1f,
|
||||
y: 100f,
|
||||
Hostile(0x50000010u));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000011u,
|
||||
landblock: 0x01010001u,
|
||||
x: 180f,
|
||||
y: 100f,
|
||||
Hostile(0x50000011u));
|
||||
|
||||
Assert.Equal(
|
||||
0x50000010u,
|
||||
RuntimeHostileTargetQuery.FindClosest(runtime));
|
||||
Assert.True(
|
||||
RuntimeHostileTargetQuery.IsHostile(
|
||||
runtime,
|
||||
0x50000010u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Query_RejectsHiddenNoDrawDeadPlayersPetsNpcsAndNonCreatures()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
Add(runtime, Player, 0x01010001u, 10f, 10f, PlayerObject(Player));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000010u,
|
||||
0x01010001u,
|
||||
11f,
|
||||
10f,
|
||||
Hostile(0x50000010u),
|
||||
PhysicsStateFlags.Hidden);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000011u,
|
||||
0x01010001u,
|
||||
12f,
|
||||
10f,
|
||||
Hostile(0x50000011u),
|
||||
PhysicsStateFlags.NoDraw);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000012u,
|
||||
0x01010001u,
|
||||
13f,
|
||||
10f,
|
||||
Hostile(0x50000012u));
|
||||
runtime.ActionOwner.Combat.OnUpdateHealth(0x50000012u, 0f);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000013u,
|
||||
0x01010001u,
|
||||
14f,
|
||||
10f,
|
||||
PlayerObject(0x50000013u));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000014u,
|
||||
0x01010001u,
|
||||
15f,
|
||||
10f,
|
||||
Hostile(0x50000014u, petOwner: Player));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000015u,
|
||||
0x01010001u,
|
||||
16f,
|
||||
10f,
|
||||
new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000015u,
|
||||
Type = ItemType.Misc,
|
||||
PublicWeenieBitfield =
|
||||
SelectedObjectHealthPolicy.BfAttackable,
|
||||
});
|
||||
Add(
|
||||
runtime,
|
||||
0x50000016u,
|
||||
0x01010001u,
|
||||
17f,
|
||||
10f,
|
||||
new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000016u,
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
Add(
|
||||
runtime,
|
||||
0x50000017u,
|
||||
0x01010001u,
|
||||
18f,
|
||||
10f,
|
||||
Hostile(0x50000017u));
|
||||
|
||||
Assert.Equal(
|
||||
0x50000017u,
|
||||
RuntimeHostileTargetQuery.FindClosest(runtime));
|
||||
Assert.False(RuntimeHostileTargetQuery.IsHostile(runtime, 0u));
|
||||
Assert.False(
|
||||
RuntimeHostileTargetQuery.IsHostile(
|
||||
runtime,
|
||||
0x50000010u));
|
||||
Assert.False(
|
||||
RuntimeHostileTargetQuery.IsHostile(
|
||||
runtime,
|
||||
0x50000012u));
|
||||
Assert.False(
|
||||
RuntimeHostileTargetQuery.IsHostile(
|
||||
runtime,
|
||||
0x50000014u));
|
||||
Assert.True(
|
||||
RuntimeHostileTargetQuery.IsHostile(
|
||||
runtime,
|
||||
0x50000017u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindClosest_ReturnsNullWithoutPlayerPositionOrHostile()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
runtime.InventoryOwner.Objects.AddOrUpdate(PlayerObject(Player));
|
||||
|
||||
Assert.Null(RuntimeHostileTargetQuery.FindClosest(runtime));
|
||||
|
||||
Add(runtime, Player, 0x01010001u, 10f, 10f, PlayerObject(Player));
|
||||
Add(
|
||||
runtime,
|
||||
0x50000010u,
|
||||
0x01010001u,
|
||||
11f,
|
||||
10f,
|
||||
new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000010u,
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
|
||||
Assert.Null(RuntimeHostileTargetQuery.FindClosest(runtime));
|
||||
}
|
||||
|
||||
private static GameRuntime Create()
|
||||
{
|
||||
var operations = new Operations();
|
||||
return new GameRuntime(new GameRuntimeDependencies(
|
||||
operations,
|
||||
operations,
|
||||
operations,
|
||||
operations));
|
||||
}
|
||||
|
||||
private static ClientObject PlayerObject(uint id) => new()
|
||||
{
|
||||
ObjectId = id,
|
||||
Type = ItemType.Creature,
|
||||
PublicWeenieBitfield =
|
||||
SelectedObjectHealthPolicy.BfPlayer,
|
||||
};
|
||||
|
||||
private static ClientObject Hostile(
|
||||
uint id,
|
||||
uint petOwner = 0u) => new()
|
||||
{
|
||||
ObjectId = id,
|
||||
Type = ItemType.Creature,
|
||||
PublicWeenieBitfield =
|
||||
SelectedObjectHealthPolicy.BfAttackable,
|
||||
PetOwnerId = petOwner,
|
||||
};
|
||||
|
||||
private static void Add(
|
||||
GameRuntime runtime,
|
||||
uint guid,
|
||||
uint landblock,
|
||||
float x,
|
||||
float y,
|
||||
ClientObject item,
|
||||
PhysicsStateFlags state = 0)
|
||||
{
|
||||
RuntimeEntityRecord record = runtime.EntityObjects
|
||||
.RegisterEntity(
|
||||
Spawn(guid, landblock, x, y, state))
|
||||
.Canonical!;
|
||||
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
|
||||
record,
|
||||
record.CreateIntegrationVersion,
|
||||
record.Snapshot,
|
||||
replaceGeneration: false));
|
||||
runtime.InventoryOwner.Objects.AddOrUpdate(item);
|
||||
}
|
||||
|
||||
private static WorldSession.EntitySpawn Spawn(
|
||||
uint guid,
|
||||
uint landblock,
|
||||
float x,
|
||||
float y,
|
||||
PhysicsStateFlags state)
|
||||
{
|
||||
var position = new CreateObject.ServerPosition(
|
||||
landblock,
|
||||
x,
|
||||
y,
|
||||
5f,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0f);
|
||||
var timestamps = new PhysicsTimestamps(
|
||||
Position: 1,
|
||||
Movement: 1,
|
||||
State: 1,
|
||||
Vector: 1,
|
||||
Teleport: 0,
|
||||
ServerControlledMove: 1,
|
||||
ForcePosition: 0,
|
||||
ObjDesc: 1,
|
||||
Instance: 1);
|
||||
var physics = new PhysicsSpawnData(
|
||||
RawState: (uint)state,
|
||||
Position: position,
|
||||
Movement: null,
|
||||
AnimationFrame: null,
|
||||
SetupTableId: 0x02000001u,
|
||||
MotionTableId: null,
|
||||
SoundTableId: null,
|
||||
PhysicsScriptTableId: null,
|
||||
Parent: null,
|
||||
Children: null,
|
||||
Scale: null,
|
||||
Friction: null,
|
||||
Elasticity: null,
|
||||
Translucency: null,
|
||||
Velocity: null,
|
||||
Acceleration: null,
|
||||
AngularVelocity: null,
|
||||
DefaultScriptType: null,
|
||||
DefaultScriptIntensity: null,
|
||||
Timestamps: timestamps);
|
||||
return new WorldSession.EntitySpawn(
|
||||
guid,
|
||||
position,
|
||||
0x02000001u,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
guid.ToString("X8"),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
PhysicsState: physics.RawState,
|
||||
InstanceSequence: 1,
|
||||
MovementSequence: 1,
|
||||
ServerControlSequence: 1,
|
||||
PositionSequence: 1,
|
||||
Physics: physics);
|
||||
}
|
||||
|
||||
private sealed class Operations :
|
||||
IRuntimeCombatAttackOperations,
|
||||
IRuntimeCombatTargetOperations,
|
||||
IRuntimeCombatModeOperations,
|
||||
IRuntimeSpellCastOperations
|
||||
{
|
||||
public bool CanStartAttack() => false;
|
||||
public void PrepareAttackRequest() { }
|
||||
public bool SendAttack(AttackHeight height, float power) => false;
|
||||
public void SendCancelAttack() { }
|
||||
public bool IsDualWield => false;
|
||||
public bool PlayerReadyForAttack => false;
|
||||
public bool AutoRepeatAttack => false;
|
||||
public bool AutoTarget => false;
|
||||
public uint? SelectClosestTarget() => null;
|
||||
public bool IsInWorld => false;
|
||||
public IReadOnlyList<ClientObject> GetOrderedEquipment() => [];
|
||||
public void NotifyExplicitCombatModeRequest() { }
|
||||
public void SendChangeCombatMode(CombatMode mode) { }
|
||||
public uint LocalPlayerId => 0u;
|
||||
public bool CanSend => false;
|
||||
public bool HasRequiredComponents(uint spellId) => false;
|
||||
public bool IsTargetCompatible(
|
||||
uint targetId,
|
||||
SpellMetadata spell,
|
||||
bool showMessage) => false;
|
||||
public void StopCompletely() { }
|
||||
public void SendUntargeted(uint spellId) { }
|
||||
public void SendTargeted(uint targetId, uint spellId) { }
|
||||
public void DisplayMessage(string message) { }
|
||||
public void IncrementBusy() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -40,14 +40,87 @@ public sealed class RuntimeLocalPlayerMovementStateTests
|
|||
public void GraphicalAndDirectCommandsMutateOneAutorunLatch()
|
||||
{
|
||||
using var movement = new RuntimeLocalPlayerMovementState();
|
||||
var input = new MovementInput(
|
||||
Forward: true,
|
||||
Run: true);
|
||||
|
||||
Assert.True(movement.Execute(RuntimeMovementCommand.ToggleRunLock));
|
||||
movement.SetCommandInput(input);
|
||||
Assert.True(movement.AutoRunActive);
|
||||
Assert.True(movement.View.Snapshot.AutoRunActive);
|
||||
Assert.True(movement.HasCommandInput);
|
||||
Assert.Equal(input, movement.CommandInput);
|
||||
Assert.Equal(input, movement.View.Snapshot.CommandInput);
|
||||
|
||||
Assert.True(movement.Execute(RuntimeMovementCommand.Stop));
|
||||
Assert.False(movement.AutoRunActive);
|
||||
Assert.False(movement.View.Snapshot.AutoRunActive);
|
||||
Assert.False(movement.HasCommandInput);
|
||||
Assert.False(movement.View.Snapshot.HasCommandInput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommandInputIsDeduplicatedAndResetWithSessionIntent()
|
||||
{
|
||||
using var movement = new RuntimeLocalPlayerMovementState();
|
||||
var input = new MovementInput(
|
||||
StrafeLeft: true,
|
||||
Run: true);
|
||||
|
||||
movement.SetCommandInput(input);
|
||||
long revision = movement.Revision;
|
||||
movement.SetCommandInput(input);
|
||||
|
||||
Assert.Equal(revision, movement.Revision);
|
||||
Assert.True(movement.Snapshot.HasCommandInput);
|
||||
|
||||
movement.ResetInputIntent();
|
||||
|
||||
Assert.False(movement.HasCommandInput);
|
||||
Assert.Equal(default, movement.CommandInput);
|
||||
Assert.Equal(revision + 1, movement.Revision);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RuntimeMovementCommand.Ready, MotionCommand.Ready)]
|
||||
[InlineData(RuntimeMovementCommand.Crouch, MotionCommand.Crouch)]
|
||||
[InlineData(RuntimeMovementCommand.Sit, MotionCommand.Sitting)]
|
||||
[InlineData(RuntimeMovementCommand.Sleep, MotionCommand.Sleeping)]
|
||||
public void StanceCommandsUseCanonicalControllerAndEmitOneMovementEdge(
|
||||
RuntimeMovementCommand command,
|
||||
uint expectedMotion)
|
||||
{
|
||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||
using var movement = new RuntimeLocalPlayerMovementState
|
||||
{
|
||||
Controller = controller,
|
||||
};
|
||||
movement.SetCommandInput(
|
||||
new MovementInput(Forward: true, Run: true));
|
||||
|
||||
Assert.True(movement.Execute(command));
|
||||
Assert.False(movement.HasCommandInput);
|
||||
Assert.Equal(
|
||||
expectedMotion,
|
||||
controller.Motion.RawState.ForwardCommand);
|
||||
|
||||
MovementResult first = controller.Update(
|
||||
1f / 60f,
|
||||
default);
|
||||
MovementResult second = controller.Update(
|
||||
1f / 60f,
|
||||
default);
|
||||
|
||||
Assert.True(first.ShouldSendMovementEvent);
|
||||
Assert.False(second.ShouldSendMovementEvent);
|
||||
if (command == RuntimeMovementCommand.Ready)
|
||||
{
|
||||
Assert.Null(first.ForwardCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedMotion, first.ForwardCommand);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -581,6 +581,8 @@ public sealed class NoWindowGameRuntimeHostTests
|
|||
throw new InvalidOperationException("observer");
|
||||
public void OnPortal(in RuntimePortalDelta delta) =>
|
||||
throw new InvalidOperationException("observer");
|
||||
public void OnCombat(in RuntimeCombatDelta delta) =>
|
||||
throw new InvalidOperationException("observer");
|
||||
}
|
||||
|
||||
private static WorldSession.EntitySpawn Spawn(
|
||||
|
|
|
|||
|
|
@ -518,6 +518,52 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(record.FullCellId, snapshot.FullCellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoteVelocityStalenessUsesInjectedRuntimeClock()
|
||||
{
|
||||
var time = new ManualTimeProvider(
|
||||
DateTimeOffset.UnixEpoch.AddSeconds(100d));
|
||||
using var lifetime = new RuntimeEntityObjectLifetime(
|
||||
timeProvider: time);
|
||||
RuntimeEntityRecord record =
|
||||
lifetime.Entities.AddActive(Spawn(0x70000025u, 1));
|
||||
var remote = new RemoteMotion
|
||||
{
|
||||
HasServerVelocity = true,
|
||||
LastServerPosTime = 99d,
|
||||
ServerVelocity = Vector3.UnitX,
|
||||
};
|
||||
remote.Body.Position = new Vector3(10f, 20f, 5f);
|
||||
remote.Body.TransientState = TransientStateFlags.Active
|
||||
| TransientStateFlags.Contact
|
||||
| TransientStateFlags.OnWalkable;
|
||||
lifetime.Physics.SetRemoteMotion(record, remote);
|
||||
lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true);
|
||||
var updater = new RuntimeRemotePhysicsUpdater(lifetime.Physics);
|
||||
Vector3? cycle = null;
|
||||
|
||||
Assert.True(updater.Tick(
|
||||
record,
|
||||
remote,
|
||||
objectScale: 1f,
|
||||
sequencer: null,
|
||||
dt: 0.1f,
|
||||
objectClockEpoch: record.ObjectClockEpoch,
|
||||
new MotionDeltaFrame
|
||||
{
|
||||
Orientation = Quaternion.Identity,
|
||||
},
|
||||
radius: 0.48f,
|
||||
height: 1.835f,
|
||||
liveCenterX: 1,
|
||||
liveCenterY: 1,
|
||||
applyStaleVelocityCycle: value => cycle = value));
|
||||
|
||||
Assert.False(remote.HasServerVelocity);
|
||||
Assert.Equal(Vector3.Zero, remote.ServerVelocity);
|
||||
Assert.Equal(Vector3.Zero, cycle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhysicsBodyAcquisitionIsCanonicalAndRejectsGuidReuse()
|
||||
{
|
||||
|
|
@ -551,6 +597,12 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.False(stale.PhysicsBodyAcquisitionInProgress);
|
||||
}
|
||||
|
||||
private sealed class ManualTimeProvider(DateTimeOffset utcNow)
|
||||
: TimeProvider
|
||||
{
|
||||
public override DateTimeOffset GetUtcNow() => utcNow;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhysicsHostIdentityAndLookupBelongToExactRuntimeIncarnation()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,11 +60,14 @@ public sealed class RuntimeGenerationResetTests
|
|||
Assert.Equal(1, host.DrainCalls);
|
||||
Assert.Equal(1, host.CompleteCalls);
|
||||
Assert.All(
|
||||
observer.Entity.Concat(observer.Inventory),
|
||||
observer.Combat
|
||||
.Concat(observer.Entity)
|
||||
.Concat(observer.Inventory),
|
||||
stamp => Assert.Equal(retiring, stamp.Generation));
|
||||
Assert.Equal(
|
||||
[1UL, 2UL],
|
||||
observer.Inventory
|
||||
[1UL, 2UL, 3UL, 4UL, 5UL],
|
||||
observer.Combat
|
||||
.Concat(observer.Inventory)
|
||||
.Concat(observer.Entity)
|
||||
.OrderBy(static stamp => stamp.Sequence)
|
||||
.Select(static stamp => stamp.Sequence));
|
||||
|
|
@ -312,6 +315,7 @@ public sealed class RuntimeGenerationResetTests
|
|||
{
|
||||
public List<RuntimeEventStamp> Entity { get; } = [];
|
||||
public List<RuntimeEventStamp> Inventory { get; } = [];
|
||||
public List<RuntimeEventStamp> Combat { get; } = [];
|
||||
|
||||
public void OnLifecycle(in RuntimeLifecycleDelta delta) { }
|
||||
public void OnCommand(in RuntimeCommandDelta delta) { }
|
||||
|
|
@ -322,6 +326,8 @@ public sealed class RuntimeGenerationResetTests
|
|||
public void OnChat(in RuntimeChatDelta delta) { }
|
||||
public void OnMovement(in RuntimeMovementDelta delta) { }
|
||||
public void OnPortal(in RuntimePortalDelta delta) { }
|
||||
public void OnCombat(in RuntimeCombatDelta delta) =>
|
||||
Combat.Add(delta.Stamp);
|
||||
}
|
||||
|
||||
private sealed class Operations :
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue