feat(net): InventoryActions — stack merge/split + give + shortcut + poi recall
Outbound GameActions for the inventory/drag-drop UI and quickbar: - StackableMerge (0x0054): u32 mergeFrom, u32 mergeTo, u32 amount. Combine two same-type stacks. - StackableSplitToContainer (0x0055): u32 stack, u32 container, u32 placement, u32 amount. Drag a portion of a stack into a pack slot. - StackableSplitTo3D (0x0056): u32 stack, u32 amount. Drop N items to the ground. - StackableSplitToWield (0x019B): u32 stack, u32 equipLoc, u32 amount. Split off and immediately equip (e.g. split an arrow stack to missile-ammo slot). - GiveObjectRequest (0x00CD): u32 target, u32 item, u32 amount. Give to NPC / other player. - AddShortcut (0x019C): u32 slot, u32 objectType, u32 targetId. Pin an item / spell to a quickbar. - RemoveShortcut (0x019D): u32 slot. Unpin. - TeleToPoi (0x00B1): u32 poiId. Quest-driven recall. Tests (8 new): byte-exact encoding of each action, including size assertions so breaking changes surface immediately. Build green, 190 Core.Net tests pass (up from 182). Ref: r08 §3 inventory / shortcut rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fa266aaa03
commit
d8c68c6648
2 changed files with 242 additions and 0 deletions
133
src/AcDream.Core.Net/Messages/InventoryActions.cs
Normal file
133
src/AcDream.Core.Net/Messages/InventoryActions.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
using System.Buffers.Binary;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Outbound inventory-manipulation GameActions: stack merge/split, give,
|
||||
/// drop, shortcuts. All ride in the <c>0xF7B1</c> GameAction envelope.
|
||||
///
|
||||
/// <para>
|
||||
/// References: r08 §3 rows 0x0054-0x0056 / 0x019B-0x019D / 0x00CD.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class InventoryActions
|
||||
{
|
||||
public const uint GameActionEnvelope = 0xF7B1u;
|
||||
|
||||
public const uint StackableMergeOpcode = 0x0054u;
|
||||
public const uint StackableSplitToContainerOpcode = 0x0055u;
|
||||
public const uint StackableSplitTo3DOpcode = 0x0056u;
|
||||
public const uint StackableSplitToWieldOpcode = 0x019Bu;
|
||||
public const uint GiveObjectRequestOpcode = 0x00CDu;
|
||||
public const uint AddShortcutOpcode = 0x019Cu;
|
||||
public const uint RemoveShortcutOpcode = 0x019Du;
|
||||
public const uint TeleToPoiOpcode = 0x00B1u;
|
||||
|
||||
/// <summary>
|
||||
/// Merge stack A into stack B of the same item type. Server validates
|
||||
/// compatibility; if the merge fails it rolls back via
|
||||
/// InventoryServerSaveFailed (0x00A0).
|
||||
/// </summary>
|
||||
public static byte[] BuildStackableMerge(uint seq, uint mergeFromGuid, uint mergeToGuid, uint amount)
|
||||
{
|
||||
byte[] body = new byte[24];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), StackableMergeOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), mergeFromGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), mergeToGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), amount);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Split N items off a stack into a container at placement.</summary>
|
||||
public static byte[] BuildStackableSplitToContainer(
|
||||
uint seq, uint stackGuid, uint containerGuid, uint placement, uint amount)
|
||||
{
|
||||
byte[] body = new byte[28];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), StackableSplitToContainerOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), stackGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), containerGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), placement);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(24), amount);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Split N items off a stack and drop them on the ground.</summary>
|
||||
public static byte[] BuildStackableSplitTo3D(uint seq, uint stackGuid, uint amount)
|
||||
{
|
||||
byte[] body = new byte[20];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), StackableSplitTo3DOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), stackGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), amount);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Split N items off a stack into an equip slot.</summary>
|
||||
public static byte[] BuildStackableSplitToWield(
|
||||
uint seq, uint stackGuid, uint equipLocation, uint amount)
|
||||
{
|
||||
byte[] body = new byte[24];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), StackableSplitToWieldOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), stackGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), equipLocation);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), amount);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Give an item (or a stack of N) to a target creature/NPC.</summary>
|
||||
public static byte[] BuildGiveObjectRequest(
|
||||
uint seq, uint targetGuid, uint itemGuid, uint amount)
|
||||
{
|
||||
byte[] body = new byte[24];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), GiveObjectRequestOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), targetGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), itemGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), amount);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Pin an item / spell to a quickbar slot.</summary>
|
||||
public static byte[] BuildAddShortcut(
|
||||
uint seq, uint slotIndex, uint objectType, uint targetId)
|
||||
{
|
||||
byte[] body = new byte[24];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), AddShortcutOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), slotIndex);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), objectType);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), targetId);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Unpin a quickbar slot.</summary>
|
||||
public static byte[] BuildRemoveShortcut(uint seq, uint slotIndex)
|
||||
{
|
||||
byte[] body = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), RemoveShortcutOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), slotIndex);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Teleport to a Point of Interest (quest-driven recall).</summary>
|
||||
public static byte[] BuildTeleToPoi(uint seq, uint poiId)
|
||||
{
|
||||
byte[] body = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), TeleToPoiOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), poiId);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue