fix(D.2b): PickupEvent removes the 3D render, not the weenie — unwield lands in the pack
PickupEvent (0xF74A) and DeleteObject (0xF747) are semantically distinct: - 0xF747 = weenie DESTROYED → evict from ClientObjectTable (weenie_object_table) - 0xF74A = object LEFT THE 3D WORLD VIEW (moved into a container) → remove the 3D WorldEntity, but the weenie persists in ClientObjectTable Before this fix, both paths fired EntityDeleted identically, causing ObjectTableWiring to evict the weenie from ClientObjectTable. The follow-up InventoryPutObjInContainer (0x0022) then tried MoveItem on an unknown guid and no-op'd, so the unwielded item simply vanished. Fix: add `bool FromPickup` (default false) to DeleteObject.Parsed. WorldSession sets it true on the PickupEvent path and false on the DeleteObject path. ObjectTableWiring.Wire's EntityDeleted handler skips table.Remove when FromPickup is true, preserving the weenie for the container-move echo. GameWindow.OnLiveEntityDeleted (3D entity removal) is untouched — it fires for both pickups and destroys, as intended. Divergence register: AP-65 added (data ghosts for other-player pickups until teleport/relog clear; harmless — no UI queries ContainerId 0). Tests: +5 (DeleteObject FromPickup parser regression; wiring retain/evict semantics; Parsed default/explicit FromPickup). 343/343 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
058da60212
commit
db0cac03c4
6 changed files with 151 additions and 13 deletions
|
|
@ -36,4 +36,42 @@ public sealed class DeleteObjectTests
|
|||
Assert.Equal(0x80000439u, parsed!.Value.Guid);
|
||||
Assert.Equal((ushort)0x1234, parsed.Value.InstanceSequence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression guard: TryParse (0xF747 = true destroy) must always produce
|
||||
/// FromPickup = false. PickupEvent (0xF74A) is a different opcode and is
|
||||
/// the ONLY path that sets FromPickup = true (in WorldSession).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryParse_AlwaysReturnsFromPickupFalse()
|
||||
{
|
||||
Span<byte> body = stackalloc byte[12];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, DeleteObject.Opcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.Slice(4), 0x80000001u);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(body.Slice(8), 0x0001);
|
||||
|
||||
var parsed = DeleteObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.False(parsed!.Value.FromPickup,
|
||||
"DeleteObject 0xF747 is a true destroy — FromPickup must be false.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FromPickup = true can be constructed via the record directly (as
|
||||
/// WorldSession does for PickupEvent 0xF74A). Default is false.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parsed_DefaultFromPickupIsFalse()
|
||||
{
|
||||
var p = new DeleteObject.Parsed(0x80000001u, 1);
|
||||
Assert.False(p.FromPickup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parsed_FromPickupTrueCanBeSet()
|
||||
{
|
||||
var p = new DeleteObject.Parsed(0x80000001u, 1, FromPickup: true);
|
||||
Assert.True(p.FromPickup);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue