acdream/src/AcDream.Core.Net/Messages/InventoryActions.cs

208 lines
10 KiB
C#

using System.Buffers.Binary;
using AcDream.Core.Items;
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;
public const uint GetAndWieldItemOpcode = 0x001Au;
public const uint DropItemOpcode = 0x001Bu;
public const uint NoLongerViewingContentsOpcode = 0x0195u;
public const uint SetInscriptionOpcode = 0x00BFu;
/// <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>
/// Pack retail <c>ShortCutData</c> for AddShortcut: signed index, object id,
/// and raw 32-bit spell word. Retail uses the same three-u32 packer as
/// <c>InventoryPlacement::Pack @ 0x005CABB0</c> through the shortcut vtable.
/// </summary>
public static byte[] BuildAddShortcut(uint seq, ShortcutEntry entry)
{
byte[] body = new byte[24];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), AddShortcutOpcode);
BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(12), entry.Index);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), entry.ObjectId);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), entry.SpellId);
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;
}
/// <summary>Drop an item on the ground. ACE GameActionDropItem.Handle reads 1 u32.</summary>
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;
}
/// <summary>Equip an item from inventory onto the doll. holtburger actions.rs
/// GetAndWieldItemActionData = (itemGuid, equipMask).</summary>
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;
}
/// <summary>Close a side-pack / ground-container view. holtburger actions.rs = (containerGuid).</summary>
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;
}
/// <summary>
/// Set or clear an item's inscription. Retail
/// <c>CM_Writing::Event_SetInscription @ 0x006A98B0</c> writes the object
/// GUID followed by one CP-1252 String16L and receives no success response.
/// </summary>
public static byte[] BuildSetInscription(
uint seq,
uint itemGuid,
string inscription)
{
ArgumentNullException.ThrowIfNull(inscription);
byte[] text = Encodings.Windows1252.GetBytes(inscription);
if (text.Length > ushort.MaxValue)
throw new ArgumentException(
"Inscription is too long for String16L.",
nameof(inscription));
int stringRecordLength = (2 + text.Length + 3) & ~3;
byte[] body = new byte[16 + stringRecordLength];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
BinaryPrimitives.WriteUInt32LittleEndian(
body.AsSpan(8),
SetInscriptionOpcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), itemGuid);
BinaryPrimitives.WriteUInt16LittleEndian(
body.AsSpan(16),
(ushort)text.Length);
text.CopyTo(body, 18);
return body;
}
}