From f8799489c2856c50488f334a20c97b36cba3f304 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 22 Jun 2026 22:19:39 +0200 Subject: [PATCH] fix(D.2b): ConfirmMove on the WieldObject 0x0023 echo (clears optimistic wield) Mirrors the InventoryPutObjInContainer 0x0022 handler which already does MoveItem + ConfirmMove. Without this, WieldItemOptimistic's pending snapshot would linger until the session ended (or incorrectly roll back on 0x00A0). ConfirmMove is a no-op when nothing is pending, so safe for server-initiated login wields. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core.Net/GameEventWiring.cs | 4 +++- .../GameEventWiringTests.cs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/AcDream.Core.Net/GameEventWiring.cs b/src/AcDream.Core.Net/GameEventWiring.cs index 8f660948..df487c77 100644 --- a/src/AcDream.Core.Net/GameEventWiring.cs +++ b/src/AcDream.Core.Net/GameEventWiring.cs @@ -238,10 +238,12 @@ public static class GameEventWiring dispatcher.Register(GameEventType.WieldObject, e => { var p = GameEvents.ParseWieldObject(e.Payload.Span); - if (p is not null) items.MoveItem( + if (p is null) return; + items.MoveItem( p.Value.ItemGuid, newContainerId: p.Value.WielderGuid, newEquipLocation: (AcDream.Core.Items.EquipMask)p.Value.EquipLoc); + items.ConfirmMove(p.Value.ItemGuid); // Slice 1: confirm an optimistic wield (mirrors the 0x0022 handler) }); dispatcher.Register(GameEventType.InventoryPutObjInContainer, e => { diff --git a/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs b/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs index ae191438..0bafb2f2 100644 --- a/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs +++ b/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs @@ -120,6 +120,25 @@ public sealed class GameEventWiringTests Assert.Equal(0x2000u, item.ContainerId); } + [Fact] + public void WireAll_WieldObject_ConfirmsOptimisticWield() + { + var (d, items, _, _, _) = MakeAll(); + const uint player = 0x2000u; + items.AddOrUpdate(new ClientObject { ObjectId = 0x1500, WeenieClassId = 1 }); + items.MoveItem(0x1500, 0x9999u, newSlot: 0); // start in a pack + items.WieldItemOptimistic(0x1500, player, EquipMask.MeleeWeapon); // optimistic wield → snapshot pending + + byte[] payload = new byte[12]; + BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x1500); + BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), (uint)EquipMask.MeleeWeapon); + BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(8), player); + var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.WieldObject, payload)); + d.Dispatch(env!.Value); // server confirms the wield + + Assert.False(items.RollbackMove(0x1500)); // pending snapshot was cleared by ConfirmMove + } + [Fact] public void WireAll_PopupString_RoutesToChatLog() {