fix(gameplay): reconcile wield ownership and target facing
Preserve PlayerDescription inventory/equipment ownership across authoritative manifest replacement, make weapon switching and combat/UI consumers read the same canonical object state, and carry the complete outbound player position frame across landblocks. Route target-facing and mouse-look through the shared MovementManager and MotionInterpreter completion owner. Match retail input aggregation, toggle ordering, turn/sidestep remapping, per-axis hold keys, and synchronous movement publication without render-only heading state. Initialize the live streaming origin from the first accepted canonical player Position, defer other projections until that origin exists, and retain logical entity identity through hydration. Advance the project ledger from completed M2 to active M3, synchronize CLAUDE.md/AGENTS.md and durable memory, and record the next cast-lifecycle, spellbook/enchantment, and two-client portal gates. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
b26f84cc69
commit
7b7ffcd278
36 changed files with 3884 additions and 761 deletions
|
|
@ -54,7 +54,11 @@ public sealed class ClientObjectTableTests
|
|||
repo.AddOrUpdate(item);
|
||||
|
||||
uint seenOld = 999, seenNew = 999;
|
||||
repo.ObjectMoved += (it, oldC, newC) => { seenOld = oldC; seenNew = newC; };
|
||||
repo.ObjectMoved += move =>
|
||||
{
|
||||
seenOld = move.Previous.ContainerId;
|
||||
seenNew = move.Current.ContainerId;
|
||||
};
|
||||
|
||||
repo.MoveItem(100, 42, newSlot: 3);
|
||||
|
||||
|
|
@ -71,6 +75,74 @@ public sealed class ClientObjectTableTests
|
|||
Assert.False(repo.MoveItem(999, 42));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyServerMove_PublishesCompleteOldAndNewRetailPlacements()
|
||||
{
|
||||
var repo = new ClientObjectTable();
|
||||
const uint player = 0x50000001u, pack = 0x40000005u, itemId = 101u;
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = itemId,
|
||||
WielderId = player,
|
||||
CurrentlyEquippedLocation = EquipMask.MissileWeapon,
|
||||
});
|
||||
ClientObjectMove? seen = null;
|
||||
repo.ObjectMoved += move => seen = move;
|
||||
|
||||
Assert.True(repo.ApplyServerMove(itemId, pack, 0u, 3));
|
||||
|
||||
Assert.Equal(
|
||||
new ClientObjectPlacement(0u, -1, player, EquipMask.MissileWeapon),
|
||||
seen!.Value.Previous);
|
||||
Assert.Equal(
|
||||
new ClientObjectPlacement(pack, 3, 0u, EquipMask.None),
|
||||
seen.Value.Current);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmedUnknownMove_PublishesGuidAndRawPlacement()
|
||||
{
|
||||
var repo = new ClientObjectTable();
|
||||
ClientObjectMove? seen = null;
|
||||
repo.ObjectMoved += move => seen = move;
|
||||
|
||||
Assert.False(repo.ApplyConfirmedServerMove(
|
||||
0xDEADu,
|
||||
0x40000001u,
|
||||
newWielderId: 0u,
|
||||
newSlot: 7));
|
||||
|
||||
Assert.Equal(0xDEADu, seen!.Value.ItemId);
|
||||
Assert.Null(seen.Value.Item);
|
||||
Assert.Equal(default, seen.Value.Previous);
|
||||
Assert.Equal(
|
||||
new ClientObjectPlacement(0x40000001u, 7, 0u, EquipMask.None),
|
||||
seen.Value.Current);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmedUnknownWield_PublishesGuidPlacementAndCompletion()
|
||||
{
|
||||
var repo = new ClientObjectTable();
|
||||
const uint itemId = 0xBEEFu, player = 0x50000001u;
|
||||
ClientObjectMove? seen = null;
|
||||
uint completed = 0u;
|
||||
repo.ObjectMoved += move => seen = move;
|
||||
repo.WieldConfirmed += id => completed = id;
|
||||
|
||||
Assert.False(repo.ApplyConfirmedServerWield(
|
||||
itemId,
|
||||
player,
|
||||
EquipMask.MissileWeapon));
|
||||
|
||||
Assert.Equal(itemId, seen!.Value.ItemId);
|
||||
Assert.Null(seen.Value.Item);
|
||||
Assert.Equal(
|
||||
new ClientObjectPlacement(0u, 0, player, EquipMask.MissileWeapon),
|
||||
seen.Value.Current);
|
||||
Assert.Equal(itemId, completed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_FiresEventAndRemoves()
|
||||
{
|
||||
|
|
@ -471,53 +543,156 @@ public sealed class ClientObjectTableTests
|
|||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u, 0xA02u, 0xA03u });
|
||||
Assert.Equal(new[] { 0xA01u, 0xA02u, 0xA03u }, table.GetContents(0xC9u));
|
||||
Assert.Equal(0xC9u, table.Get(0xA01u)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(0xA01u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_SecondSmallerList_DetachesDropped() // the full-REPLACE semantics (vs the old additive merge)
|
||||
public void ReplaceContents_PublishesListReplacementWithoutSyntheticMove()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
int moves = 0;
|
||||
uint replaced = 0u;
|
||||
table.ObjectMoved += _ => moves++;
|
||||
table.ContainerContentsReplaced += container => replaced = container;
|
||||
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u });
|
||||
|
||||
Assert.Equal(0, moves);
|
||||
Assert.Equal(0xC9u, replaced);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectionOnlyMember_AuthoritativeMoveEvictsStaleViewedList()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint chest = 0x40000001u, pack = 0x50000001u, item = 0xA20u;
|
||||
table.ReplaceContents(chest, new uint[] { item });
|
||||
|
||||
Assert.True(table.ApplyServerMove(item, pack, newWielderId: 0u, newSlot: 0));
|
||||
|
||||
Assert.Empty(table.GetContents(chest));
|
||||
Assert.Equal(new[] { item }, table.GetContents(pack));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectionEvictionNotification_ReentrantMoveSeesCommittedState()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint chest = 0x40000001u, packA = 0x50000001u;
|
||||
const uint packB = 0x50000002u, item = 0xA22u;
|
||||
table.ReplaceContents(chest, new uint[] { item });
|
||||
table.ContainerContentsReplaced += containerId =>
|
||||
{
|
||||
if (containerId == chest)
|
||||
Assert.True(table.ApplyServerMove(item, packB, newWielderId: 0u, newSlot: 0));
|
||||
};
|
||||
|
||||
Assert.True(table.ApplyServerMove(item, packA, newWielderId: 0u, newSlot: 0));
|
||||
|
||||
Assert.Empty(table.GetContents(chest));
|
||||
Assert.Empty(table.GetContents(packA));
|
||||
Assert.Equal(new[] { item }, table.GetContents(packB));
|
||||
Assert.Equal(packB, table.Get(item)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectionOnlyMember_DeleteEvictsStaleViewedListAndNotifies()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint chest = 0x40000001u, item = 0xA21u;
|
||||
table.ReplaceContents(chest, new uint[] { item });
|
||||
int replacements = 0;
|
||||
table.ContainerContentsReplaced += id =>
|
||||
{
|
||||
if (id == chest) replacements++;
|
||||
};
|
||||
|
||||
Assert.True(table.Remove(item));
|
||||
|
||||
Assert.Empty(table.GetContents(chest));
|
||||
Assert.Equal(1, replacements);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteProjectionNotification_GuidReuseSurvivesCompletedTeardown()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint chest = 0x40000001u, item = 0xA23u, player = 0x50000001u;
|
||||
table.ReplaceContents(chest, new uint[] { item });
|
||||
table.ContainerContentsReplaced += containerId =>
|
||||
{
|
||||
if (containerId != chest) return;
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = item,
|
||||
WielderId = player,
|
||||
CurrentlyEquippedLocation = EquipMask.MissileWeapon,
|
||||
});
|
||||
};
|
||||
|
||||
Assert.True(table.Remove(item));
|
||||
|
||||
Assert.NotNull(table.Get(item));
|
||||
Assert.Equal(
|
||||
new[] { item },
|
||||
table.GetEquippedBy(player).Select(equipped => equipped.ObjectId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_SecondSmallerList_ReplacesProjectionOnly()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u, 0xA02u, 0xA03u });
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA02u }); // A01 + A03 removed server-side
|
||||
Assert.Equal(new[] { 0xA02u }, table.GetContents(0xC9u));
|
||||
Assert.Equal(0u, table.Get(0xA01u)!.ContainerId); // detached, not lingering
|
||||
Assert.Equal(0u, table.Get(0xA01u)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(0xA03u)!.ContainerId);
|
||||
Assert.Equal(0xC9u, table.Get(0xA02u)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(0xA02u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_listedMemberClearsStaleEquipLocation()
|
||||
public void ReplaceContents_listedMemberPreservesCanonicalPlacement()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0xA10u,
|
||||
ContainerId = 0x50000001u,
|
||||
ContainerId = 0u,
|
||||
WielderId = 0x50000001u,
|
||||
CurrentlyEquippedLocation = EquipMask.ChestWear | EquipMask.UpperArmWear,
|
||||
});
|
||||
|
||||
table.ReplaceContents(0x50000001u, new uint[] { 0xA10u });
|
||||
|
||||
var item = table.Get(0xA10u)!;
|
||||
Assert.Equal(0x50000001u, item.ContainerId);
|
||||
Assert.Equal(0, item.ContainerSlot);
|
||||
Assert.Equal(EquipMask.None, item.CurrentlyEquippedLocation);
|
||||
Assert.Equal(0u, item.ContainerId);
|
||||
Assert.Equal(0x50000001u, item.WielderId);
|
||||
Assert.Equal(-1, item.ContainerSlot);
|
||||
Assert.Equal(
|
||||
EquipMask.ChestWear | EquipMask.UpperArmWear,
|
||||
item.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_detachedMemberClearsStaleEquipLocation()
|
||||
public void ReplaceContents_omittedMemberPreservesCanonicalPlacement()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.AddOrUpdate(new ClientObject { ObjectId = 0xA11u });
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0xA11u,
|
||||
WielderId = 0x50000001u,
|
||||
});
|
||||
table.MoveItem(0xA11u, 0x50000001u, newSlot: 0,
|
||||
newEquipLocation: EquipMask.ChestWear | EquipMask.UpperArmWear);
|
||||
|
||||
table.ReplaceContents(0x50000001u, Array.Empty<uint>());
|
||||
|
||||
var item = table.Get(0xA11u)!;
|
||||
Assert.Equal(0u, item.ContainerId);
|
||||
Assert.Equal(EquipMask.None, item.CurrentlyEquippedLocation);
|
||||
Assert.Equal(0x50000001u, item.ContainerId);
|
||||
Assert.Equal(0x50000001u, item.WielderId);
|
||||
Assert.Equal(
|
||||
EquipMask.ChestWear | EquipMask.UpperArmWear,
|
||||
item.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -559,11 +734,130 @@ public sealed class ClientObjectTableTests
|
|||
table.Ingest(FullWeenie(0xA01u, container: player));
|
||||
|
||||
Assert.Equal(new[] { 0xA01u, 0xA02u }, table.GetContents(player));
|
||||
Assert.Equal(0, table.Get(0xA01u)!.ContainerSlot);
|
||||
Assert.Equal(1, table.Get(0xA02u)!.ContainerSlot);
|
||||
Assert.Equal(-1, table.Get(0xA01u)!.ContainerSlot);
|
||||
Assert.Equal(-1, table.Get(0xA02u)!.ContainerSlot);
|
||||
Assert.Equal(1u, table.Get(0xA02u)!.ContainerTypeHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeInventoryManifest_SeedsCanonicalOwnershipAndOrder()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
|
||||
table.InitializeInventoryManifest(player, new[]
|
||||
{
|
||||
new ContainerContentEntry(0xA01u, 0u),
|
||||
new ContainerContentEntry(0xA02u, 1u),
|
||||
});
|
||||
|
||||
Assert.Equal(new[] { 0xA01u, 0xA02u }, table.GetContents(player));
|
||||
Assert.Equal(player, table.Get(0xA01u)!.ContainerId);
|
||||
Assert.Equal(0, table.Get(0xA01u)!.ContainerSlot);
|
||||
Assert.Equal(player, table.Get(0xA02u)!.ContainerId);
|
||||
Assert.Equal(1, table.Get(0xA02u)!.ContainerSlot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeEquipmentManifest_PreservesPackedOrderAndCanonicalOwnership()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
|
||||
table.InitializeEquipmentManifest(player, new[]
|
||||
{
|
||||
new EquipmentManifestEntry(0xA11u, EquipMask.MeleeWeapon, 1u),
|
||||
new EquipmentManifestEntry(0xA12u, EquipMask.MissileAmmo, 2u),
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
new[] { 0xA11u, 0xA12u },
|
||||
table.GetEquippedBy(player).Select(item => item.ObjectId));
|
||||
Assert.Equal(0u, table.Get(0xA11u)!.ContainerId);
|
||||
Assert.Equal(player, table.Get(0xA11u)!.WielderId);
|
||||
Assert.Empty(table.GetContents(player));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeEquipmentManifest_ReplacementClearsOmittedCanonicalEquipment()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
const uint weapon = 0xA11u;
|
||||
|
||||
table.InitializeEquipmentManifest(player, new[]
|
||||
{
|
||||
new EquipmentManifestEntry(weapon, EquipMask.MeleeWeapon, 1u),
|
||||
});
|
||||
table.Get(weapon)!.Burden = 10;
|
||||
|
||||
table.InitializeEquipmentManifest(player, Array.Empty<EquipmentManifestEntry>());
|
||||
|
||||
ClientObject item = table.Get(weapon)!;
|
||||
Assert.Equal(0u, item.WielderId);
|
||||
Assert.Equal(0u, item.ContainerId);
|
||||
Assert.Equal(-1, item.ContainerSlot);
|
||||
Assert.Equal(EquipMask.None, item.CurrentlyEquippedLocation);
|
||||
Assert.Empty(table.GetEquippedBy(player));
|
||||
Assert.Equal(0, table.SumCarriedBurden(player));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeInventoryManifest_ReplacementClearsOmittedCanonicalOwnership()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
const uint itemId = 0xA01u;
|
||||
|
||||
table.InitializeInventoryManifest(player, new[]
|
||||
{
|
||||
new ContainerContentEntry(itemId, 0u),
|
||||
});
|
||||
table.Get(itemId)!.Burden = 10;
|
||||
|
||||
table.InitializeInventoryManifest(player, Array.Empty<ContainerContentEntry>());
|
||||
|
||||
ClientObject item = table.Get(itemId)!;
|
||||
Assert.Equal(0u, item.ContainerId);
|
||||
Assert.Equal(-1, item.ContainerSlot);
|
||||
Assert.Equal(0u, item.WielderId);
|
||||
Assert.Equal(EquipMask.None, item.CurrentlyEquippedLocation);
|
||||
Assert.Empty(table.GetContents(player));
|
||||
Assert.Equal(0, table.SumCarriedBurden(player));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlayerDescriptionManifests_MoveItemBetweenInventoryAndEquipment()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
const uint weapon = 0xA11u;
|
||||
|
||||
table.InitializeInventoryManifest(player, new[]
|
||||
{
|
||||
new ContainerContentEntry(weapon, 0u),
|
||||
});
|
||||
table.InitializeEquipmentManifest(player, new[]
|
||||
{
|
||||
new EquipmentManifestEntry(weapon, EquipMask.MeleeWeapon, 1u),
|
||||
});
|
||||
|
||||
Assert.Empty(table.GetContents(player));
|
||||
Assert.Equal(player, table.Get(weapon)!.WielderId);
|
||||
Assert.Equal(EquipMask.MeleeWeapon, table.Get(weapon)!.CurrentlyEquippedLocation);
|
||||
|
||||
table.InitializeEquipmentManifest(player, Array.Empty<EquipmentManifestEntry>());
|
||||
table.InitializeInventoryManifest(player, new[]
|
||||
{
|
||||
new ContainerContentEntry(weapon, 0u),
|
||||
});
|
||||
|
||||
Assert.Empty(table.GetEquippedBy(player));
|
||||
Assert.Equal(new[] { weapon }, table.GetContents(player));
|
||||
Assert.Equal(player, table.Get(weapon)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(weapon)!.WielderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reindex_UnknownSlotAppendsAfterKnownSnapshotSlots()
|
||||
{
|
||||
|
|
@ -692,8 +986,8 @@ public sealed class ClientObjectTableTests
|
|||
table.WieldItemOptimistic(0x940u, player, EquipMask.HeadWear);
|
||||
var o = table.Get(0x940u)!;
|
||||
Assert.Equal(EquipMask.HeadWear, o.CurrentlyEquippedLocation); // equipped now (instant)
|
||||
Assert.Equal(player, o.ContainerId); // contained-by-wielder (matches the WieldObject 0x0023 confirm)
|
||||
Assert.Equal(0u, o.WielderId); // optimistic wield does NOT write WielderId (ContainerId-based model)
|
||||
Assert.Equal(player, o.ContainerId); // temporary pre-confirm placement projection
|
||||
Assert.Equal(0u, o.WielderId); // authoritative WieldObject later sets WielderId + clears ContainerId
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -731,6 +1025,89 @@ public sealed class ClientObjectTableTests
|
|||
public void WieldItemOptimistic_unknownItem_false()
|
||||
=> Assert.False(new ClientObjectTable().WieldItemOptimistic(0xDEADu, 0x1u, EquipMask.HeadWear));
|
||||
|
||||
[Fact]
|
||||
public void ApplyServerMove_UpdatesAuthoritativeWielderAcrossWieldAndUnwield()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint item = 0x9430u, player = 0x50000001u, pack = 0x40000005u;
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = item,
|
||||
ContainerId = player,
|
||||
WielderId = player,
|
||||
CurrentlyEquippedLocation = EquipMask.MissileWeapon,
|
||||
});
|
||||
|
||||
Assert.True(table.ApplyServerMove(item, pack, newWielderId: 0u, newSlot: 2));
|
||||
Assert.Equal(pack, table.Get(item)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(item)!.WielderId);
|
||||
Assert.Equal(EquipMask.None, table.Get(item)!.CurrentlyEquippedLocation);
|
||||
|
||||
Assert.True(table.ApplyServerMove(
|
||||
item,
|
||||
newContainerId: 0u,
|
||||
newWielderId: player,
|
||||
newEquipLocation: EquipMask.MissileWeapon));
|
||||
Assert.Equal(0u, table.Get(item)!.ContainerId);
|
||||
Assert.Equal(player, table.Get(item)!.WielderId);
|
||||
Assert.Equal(EquipMask.MissileWeapon, table.Get(item)!.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyConfirmedServerWield_ReentrantMoveKeepsNewPendingRequest()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint item = 0x9431u, player = 0x50000001u, pack = 0x40000005u;
|
||||
table.AddOrUpdate(new ClientObject { ObjectId = item, ContainerId = pack, ContainerSlot = 0 });
|
||||
Assert.True(table.WieldItemOptimistic(item, player, EquipMask.MissileWeapon));
|
||||
|
||||
bool queuedFromNotification = false;
|
||||
table.ObjectMoved += move =>
|
||||
{
|
||||
if (queuedFromNotification || move.Current.WielderId != player) return;
|
||||
queuedFromNotification = true;
|
||||
Assert.True(table.MoveItemOptimistic(item, pack, newSlot: 0));
|
||||
};
|
||||
|
||||
Assert.True(table.ApplyConfirmedServerWield(item, player, EquipMask.MissileWeapon));
|
||||
Assert.True(queuedFromNotification);
|
||||
Assert.True(table.RollbackMove(item));
|
||||
Assert.Equal(0u, table.Get(item)!.ContainerId);
|
||||
Assert.Equal(player, table.Get(item)!.WielderId);
|
||||
Assert.Equal(EquipMask.MissileWeapon, table.Get(item)!.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEquippedBy_PreservesRetailHeadInsertionOrder()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint player = 0x50000001u;
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0x9432u,
|
||||
ContainerId = 0u,
|
||||
WielderId = player,
|
||||
CurrentlyEquippedLocation = EquipMask.MissileWeapon,
|
||||
});
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0x9433u,
|
||||
ContainerId = player,
|
||||
WielderId = 0u,
|
||||
CurrentlyEquippedLocation = EquipMask.MissileAmmo,
|
||||
});
|
||||
table.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0x9434u,
|
||||
ContainerId = player,
|
||||
CurrentlyEquippedLocation = EquipMask.None,
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
new[] { 0x9433u, 0x9432u },
|
||||
table.GetEquippedBy(player).Select(item => item.ObjectId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectMove_withoutOptimisticMutation_stillPublishesServerFailure()
|
||||
{
|
||||
|
|
@ -766,7 +1143,7 @@ public sealed class ClientObjectTableTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmWield_publishesIndependentlyOfRollbackOutstandingCount()
|
||||
public void ApplyConfirmedServerWield_publishesIndependentlyOfRollbackOutstandingCount()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
const uint item = 0x944u, player = 0x50000001u, pack = 0x40000005u;
|
||||
|
|
@ -775,12 +1152,20 @@ public sealed class ClientObjectTableTests
|
|||
table.WieldItemOptimistic(item, player, EquipMask.MeleeWeapon);
|
||||
table.MoveItemOptimistic(item, pack, 0);
|
||||
var confirmed = new List<uint>();
|
||||
table.WieldConfirmed += moved => confirmed.Add(moved.ObjectId);
|
||||
table.WieldConfirmed += itemId => confirmed.Add(itemId);
|
||||
|
||||
table.ConfirmWield(item);
|
||||
Assert.True(table.ApplyConfirmedServerWield(
|
||||
item,
|
||||
player,
|
||||
EquipMask.MeleeWeapon));
|
||||
|
||||
Assert.Equal(new[] { item }, confirmed);
|
||||
Assert.Equal(0u, table.Get(item)!.ContainerId);
|
||||
Assert.Equal(player, table.Get(item)!.WielderId);
|
||||
Assert.True(table.RollbackMove(item));
|
||||
Assert.Equal(pack, table.Get(item)!.ContainerId);
|
||||
Assert.Equal(0u, table.Get(item)!.WielderId);
|
||||
Assert.Equal(EquipMask.None, table.Get(item)!.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue