From de8baa1aa1766e31eed57271ccbf1acc25a56254 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 18:58:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(net):=20D.2b-B=20B-Wire=20=E2=80=94=20Drop?= =?UTF-8?q?Item/GetAndWieldItem/NoLongerViewingContents=20builders=20+=20S?= =?UTF-8?q?end=20wrappers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 9 of the B-Wire inventory-wire plan. Adds three outbound C→S GameAction builders to InventoryActions.cs (DropItem 0x001B, GetAndWieldItem 0x001A, NoLongerViewingContents 0x0195) plus matching opcode constants after TeleToPoiOpcode. Adds SendDropItem/SendGetAndWieldItem/SendNoLongerViewingContents wrappers to WorldSession.cs (after SendRemoveShortcut, ~line 1157), following the exact same NextGameActionSequence() + SendGameAction() pattern as the existing Send* family. Three byte-layout tests added to InventoryActionsTests.cs (12 total, all green). Core.Net build clean. Unblocks B-Drag (drop) and container-open (NoLongerViewingContents on close) call sites in the inventory controller. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Messages/InventoryActions.cs | 38 +++++++++++++++++++ src/AcDream.Core.Net/WorldSession.cs | 21 ++++++++++ .../Messages/InventoryActionsTests.cs | 34 +++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/src/AcDream.Core.Net/Messages/InventoryActions.cs b/src/AcDream.Core.Net/Messages/InventoryActions.cs index 72fe566c..cebe9a82 100644 --- a/src/AcDream.Core.Net/Messages/InventoryActions.cs +++ b/src/AcDream.Core.Net/Messages/InventoryActions.cs @@ -22,6 +22,9 @@ public static class InventoryActions public const uint AddShortcutOpcode = 0x019Cu; public const uint RemoveShortcutOpcode = 0x019Du; public const uint TeleToPoiOpcode = 0x00B1u; + public const uint GetAndWieldItemOpcode = 0x001Au; + public const uint DropItemOpcode = 0x001Bu; + public const uint NoLongerViewingContentsOpcode = 0x0195u; /// /// Merge stack A into stack B of the same item type. Server validates @@ -133,4 +136,39 @@ public static class InventoryActions BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), poiId); return body; } + + /// Drop an item on the ground. ACE GameActionDropItem.Handle reads 1 u32. + public static byte[] BuildDropItem(uint seq, uint itemGuid) + { + byte[] body = new byte[16]; + BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), DropItemOpcode); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), itemGuid); + return body; + } + + /// Equip an item from inventory onto the doll. holtburger actions.rs + /// GetAndWieldItemActionData = (itemGuid, equipMask). + public static byte[] BuildGetAndWieldItem(uint seq, uint itemGuid, uint equipMask) + { + byte[] body = new byte[20]; + BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), GetAndWieldItemOpcode); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), itemGuid); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), equipMask); + return body; + } + + /// Close a side-pack / ground-container view. holtburger actions.rs = (containerGuid). + public static byte[] BuildNoLongerViewingContents(uint seq, uint containerGuid) + { + byte[] body = new byte[16]; + BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), NoLongerViewingContentsOpcode); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), containerGuid); + return body; + } } diff --git a/src/AcDream.Core.Net/WorldSession.cs b/src/AcDream.Core.Net/WorldSession.cs index 3f6dbbe2..d00edbd9 100644 --- a/src/AcDream.Core.Net/WorldSession.cs +++ b/src/AcDream.Core.Net/WorldSession.cs @@ -1156,6 +1156,27 @@ public sealed class WorldSession : IDisposable SendGameAction(InventoryActions.BuildRemoveShortcut(seq, index)); } + /// Send DropItem (0x001B) — drop an item on the ground. + public void SendDropItem(uint itemGuid) + { + uint seq = NextGameActionSequence(); + SendGameAction(InventoryActions.BuildDropItem(seq, itemGuid)); + } + + /// Send GetAndWieldItem (0x001A) — equip an item to an equip slot. + public void SendGetAndWieldItem(uint itemGuid, uint equipMask) + { + uint seq = NextGameActionSequence(); + SendGameAction(InventoryActions.BuildGetAndWieldItem(seq, itemGuid, equipMask)); + } + + /// Send NoLongerViewingContents (0x0195) — close a container view. + public void SendNoLongerViewingContents(uint containerGuid) + { + uint seq = NextGameActionSequence(); + SendGameAction(InventoryActions.BuildNoLongerViewingContents(seq, containerGuid)); + } + /// Send retail QueryHealth (0x01BF). Server replies UpdateHealth (0x01C0). /// /// Retail anchor: CM_Combat::Event_QueryHealth / gmToolbarUI::HandleSelectionChanged:198635 diff --git a/tests/AcDream.Core.Net.Tests/Messages/InventoryActionsTests.cs b/tests/AcDream.Core.Net.Tests/Messages/InventoryActionsTests.cs index 298448ae..750147d0 100644 --- a/tests/AcDream.Core.Net.Tests/Messages/InventoryActionsTests.cs +++ b/tests/AcDream.Core.Net.Tests/Messages/InventoryActionsTests.cs @@ -113,4 +113,38 @@ public sealed class InventoryActionsTests Assert.Equal(42u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12))); } + + [Fact] + public void BuildDropItem_layout() + { + byte[] b = InventoryActions.BuildDropItem(seq: 7, itemGuid: 0x50000A01u); + Assert.Equal(16, b.Length); + Assert.Equal(0xF7B1u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(0))); + Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(4))); + Assert.Equal(0x001Bu, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(8))); + Assert.Equal(0x50000A01u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(12))); + } + + [Fact] + public void BuildGetAndWieldItem_layout() + { + byte[] b = InventoryActions.BuildGetAndWieldItem(seq: 7, itemGuid: 0x50000A01u, equipMask: 0x02u); + Assert.Equal(20, b.Length); + Assert.Equal(0xF7B1u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(0))); + Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(4))); + Assert.Equal(0x001Au, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(8))); + Assert.Equal(0x50000A01u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(12))); + Assert.Equal(0x02u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(16))); + } + + [Fact] + public void BuildNoLongerViewingContents_layout() + { + byte[] b = InventoryActions.BuildNoLongerViewingContents(seq: 7, containerGuid: 0x500000C9u); + Assert.Equal(16, b.Length); + Assert.Equal(0xF7B1u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(0))); + Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(4))); + Assert.Equal(0x0195u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(8))); + Assert.Equal(0x500000C9u, BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(12))); + } }