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

@ -76,7 +76,8 @@ public static class GameEventWiring
Action<GameEvents.CharacterConfirmationDone>? onConfirmationDone = null,
FriendsState? friends = null,
SquelchState? squelch = null,
Action<IReadOnlyList<(uint Id, uint Amount)>>? onDesiredComponents = null)
Action<IReadOnlyList<(uint Id, uint Amount)>>? onDesiredComponents = null,
Action<uint /*options1*/, uint /*options2*/>? onCharacterOptions = null)
{
ArgumentNullException.ThrowIfNull(dispatcher);
ArgumentNullException.ThrowIfNull(items);
@ -439,6 +440,7 @@ public static class GameEventWiring
Console.WriteLine($"vitals: PlayerDescription body.len={e.Payload.Length} parsed={(p is null ? "NULL" : $"vec={p.Value.VectorFlags} attrs={p.Value.Attributes.Count} spells={p.Value.Spells.Count}")}");
if (p is null) return;
onCharacterOptions?.Invoke(p.Value.Options1, p.Value.Options2);
onDesiredComponents?.Invoke(p.Value.DesiredComps);
// B-Wire: deliver the player's OWN properties to the player ClientObject.

View file

@ -197,6 +197,20 @@ public static class PlayerDescriptionParser
SpellLists8 = 0x00000400,
}
/// <summary>
/// Character-option bits consumed by the client runtime. Values are the
/// verbatim retail <c>CharacterOption</c> enum from
/// <c>named-retail/acclient.h:3404</c>.
/// </summary>
[Flags]
public enum CharacterOptions1 : uint
{
None = 0,
AllowGive = 0x00000040,
DragItemOnPlayerOpensSecureTrade = 0x04000000,
Default = 0x50C4A54A,
}
/// <summary>One inventory entry — a guid plus a ContainerType
/// discriminator (0=NonContainer, 1=Container, 2=Foci). Holtburger
/// <c>events.rs:143-168</c> validates <c>ContainerType &lt;= 2</c>

View file

@ -1479,6 +1479,18 @@ public sealed class WorldSession : IDisposable
SendGameAction(InventoryActions.BuildDropItem(seq, itemGuid));
}
/// <summary>
/// Send retail GiveObjectRequest (0x00CD). Retail
/// <c>CM_Inventory::Event_GiveObjectRequest @ 0x006ABB00</c> writes
/// target, source item, then selected stack amount.
/// </summary>
public void SendGiveObject(uint targetGuid, uint itemGuid, uint amount)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildGiveObjectRequest(
seq, targetGuid, itemGuid, amount));
}
/// <summary>Send GetAndWieldItem (0x001A) — equip an item to an equip slot.</summary>
public void SendGetAndWieldItem(uint itemGuid, uint equipMask)
{