fix(interaction): restore retail loot placement and world-drop projection

This commit is contained in:
Erik 2026-07-27 00:03:15 +02:00
parent 4d095be286
commit 921712f412
24 changed files with 1581 additions and 34 deletions

View file

@ -46,6 +46,49 @@ public sealed class ClientObjectTableTests
Assert.Equal(1, propUpdateCount);
}
[Fact]
public void IsOwnedByObjectMatchesRetailDirectPackAndLocationRules()
{
const uint player = 0x5000_0001u;
const uint pack = 0x4000_0001u;
var repo = new ClientObjectTable();
repo.AddOrUpdate(MakeItem(player, "Player"));
repo.AddOrUpdate(new ClientObject
{
ObjectId = pack,
Name = "Pack",
Type = ItemType.Container,
ContainerId = player,
});
repo.ReplaceContents(
player,
[new ContainerContentEntry(pack, ContainerType: 1u)]);
ClientObject loose = MakeItem(100u, "Loose");
loose.ContainerId = player;
repo.AddOrUpdate(loose);
ClientObject packed = MakeItem(101u, "Packed");
packed.ContainerId = pack;
repo.AddOrUpdate(packed);
ClientObject equipped = MakeItem(102u, "Equipped");
equipped.WielderId = player;
equipped.CurrentlyEquippedLocation = EquipMask.MeleeWeapon;
repo.AddOrUpdate(equipped);
ClientObject foreign = MakeItem(103u, "Foreign");
foreign.ContainerId = 0x6000_0001u;
repo.AddOrUpdate(foreign);
Assert.True(repo.IsOwnedByObject(player, player));
Assert.True(repo.IsOwnedByObject(loose.ObjectId, player));
Assert.True(repo.IsOwnedByObject(packed.ObjectId, player));
Assert.True(repo.IsOwnedByObject(equipped.ObjectId, player));
Assert.False(repo.IsOwnedByObject(foreign.ObjectId, player));
Assert.False(repo.IsOwnedByObject(packed.ObjectId, 0u));
}
[Fact]
public void StopViewingContents_RemovesOnlyTemporaryProjection()
{
@ -673,6 +716,96 @@ public sealed class ClientObjectTableTests
Assert.Equal(new[] { item }, table.GetContents(pack));
}
[Fact]
public void AuthoritativePickup_PlacementZeroInsertsAtRetailListHead()
{
var table = new ClientObjectTable();
const uint corpse = 0x40000001u;
const uint pack = 0x50000001u;
const uint existingA = 0xA01u;
const uint existingB = 0xA02u;
const uint firstLoot = 0xA03u;
const uint secondLoot = 0xA04u;
table.InitializeInventoryManifest(pack, new[]
{
new ContainerContentEntry(existingA, 0u),
new ContainerContentEntry(existingB, 0u),
});
table.ReplaceContents(corpse, new[] { firstLoot, secondLoot });
Assert.True(table.ApplyConfirmedServerMove(
firstLoot,
pack,
newWielderId: 0u,
newSlot: 0,
containerTypeHint: 0u));
Assert.Equal(
new[] { firstLoot, existingA, existingB },
table.GetContents(pack));
Assert.Equal(
new[] { 0, 1, 2 },
table.GetContents(pack).Select(id => table.Get(id)!.ContainerSlot));
Assert.True(table.ApplyConfirmedServerMove(
secondLoot,
pack,
newWielderId: 0u,
newSlot: 0,
containerTypeHint: 0u));
Assert.Equal(
new[] { secondLoot, firstLoot, existingA, existingB },
table.GetContents(pack));
Assert.Equal(
new[] { 0, 1, 2, 3 },
table.GetContents(pack).Select(id => table.Get(id)!.ContainerSlot));
Assert.Empty(table.GetContents(corpse));
}
[Fact]
public void AuthoritativeContainerMove_IndexesOnlyTheRetailContainerList()
{
var table = new ClientObjectTable();
const uint pack = 0x50000001u;
const uint looseA = 0xA11u;
const uint looseB = 0xA12u;
const uint bagA = 0xA21u;
const uint bagB = 0xA22u;
table.InitializeInventoryManifest(pack, new[]
{
new ContainerContentEntry(looseA, 0u),
new ContainerContentEntry(bagA, 1u),
new ContainerContentEntry(looseB, 0u),
});
table.AddOrUpdate(new ClientObject
{
ObjectId = bagB,
Type = ItemType.Container,
ItemsCapacity = 24,
});
Assert.True(table.ApplyConfirmedServerMove(
bagB,
pack,
newWielderId: 0u,
newSlot: 0,
containerTypeHint: 1u));
uint[] loose = table.GetContents(pack)
.Where(id => table.Get(id)!.ContainerTypeHint == 0u
&& (table.Get(id)!.Type & ItemType.Container) == 0)
.ToArray();
uint[] bags = table.GetContents(pack)
.Where(id => table.Get(id)!.ContainerTypeHint != 0u
|| (table.Get(id)!.Type & ItemType.Container) != 0)
.ToArray();
Assert.Equal(new[] { looseA, looseB }, loose);
Assert.Equal(new[] { bagB, bagA }, bags);
Assert.Equal(0, table.Get(bagB)!.ContainerSlot);
Assert.Equal(1, table.Get(bagA)!.ContainerSlot);
}
[Fact]
public void ProjectionEvictionNotification_ReentrantMoveSeesCommittedState()
{