feat(inventory): port retail item giving (#216)

Preserve SmartBox drag-release coordinates, route the picked 3-D target through the retail AttemptPlaceIn3D policy, and send authoritative GiveObject requests with the selected stack quantity. Honor PlayerDescription's player secure-trade option and document the remaining trade-system boundary.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-13 20:28:38 +02:00
parent a944e49b9c
commit 30d294506c
13 changed files with 421 additions and 10 deletions

View file

@ -990,4 +990,39 @@ public sealed class GameEventWiringTests
Assert.False(items.RollbackMove(0x50000A03u));
}
[Fact]
public void WireAll_PlayerDescription_PublishesCharacterOptions()
{
var dispatcher = new GameEventDispatcher();
(uint Options1, uint Options2)? observed = null;
GameEventWiring.WireAll(
dispatcher,
new ClientObjectTable(),
new CombatState(),
new Spellbook(),
new ChatLog(),
onCharacterOptions: (options1, options2) =>
observed = (options1, options2));
var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
writer.Write(0u); // property flags
writer.Write(0x52u); // player weenie type
writer.Write(0u); // vector flags
writer.Write(0u); // has health
writer.Write(0x40u); // option flags: CharacterOptions2
writer.Write(0x50C4A54Au); // options1
writer.Write(0u); // legacy hotbar count
writer.Write(0u); // spellbook filters
writer.Write(0x948700u); // options2
writer.Write(0u); // inventory count
writer.Write(0u); // equipped count
var envelope = GameEventEnvelope.TryParse(
WrapEnvelope(GameEventType.PlayerDescription, stream.ToArray()));
dispatcher.Dispatch(envelope!.Value);
Assert.Equal((0x50C4A54Au, 0x948700u), observed);
}
}

View file

@ -45,4 +45,19 @@ public sealed class WorldSessionInventoryActionTests
Assert.Equal(InventoryActions.BuildStackableSplitTo3D(1, 0xAAu, 2u), captured);
}
[Fact]
public void SendGiveObject_UsesNextSequenceAndExactBuilderBytes()
{
using var session = new WorldSession(
new IPEndPoint(IPAddress.Loopback, 65000));
byte[]? captured = null;
session.GameActionCapture = body => captured = body;
session.SendGiveObject(targetGuid: 0xAAu, itemGuid: 0xBBu, amount: 3u);
Assert.Equal(
InventoryActions.BuildGiveObjectRequest(1, 0xAAu, 0xBBu, 3u),
captured);
}
}