feat(net): 9 more GameEvent payload parsers

Extends GameEvents.cs with parsers for commonly-observed events that
had been routed-but-unparsed by the F.1 dispatcher:

- UseDone (0x01C7): u32 weenieError — Use/UseWithTarget completion.
- InventoryPutObjectIn3D (0x019A): u32 itemGuid — server dropped item.
- InventoryServerSaveFailed (0x00A0): u32 itemGuid — rollback signal.
- CloseGroundContainer (0x0052): u32 containerGuid.
- TradeFailure (0x0207): u32 errorCode.
- AddToTrade (0x0200): (itemGuid, slotIndex).
- AcceptTrade (0x0202): u32 initiatorGuid.
- QueryItemManaResponse (0x0264): (itemGuid, f32 manaPercent).
- CharacterConfirmationRequest (0x0274): (type, contextId, otherGuid,
  string16L message) — server-driven modal confirmations.

All defensive: return null on truncated payloads rather than throwing,
matching the existing style. Caller can keep the dispatcher alive even
on malformed events.

Build green, tests unchanged (no new tests — these are simple passthroughs).

Ref: r08 §4 payloads for each opcode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-19 10:29:56 +02:00
parent d8c68c6648
commit 3fd9f264b7

View file

@ -375,6 +375,94 @@ public static class GameEvents
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(8)));
}
// ── Other small-payload events ──────────────────────────────────────────
/// <summary>0x01C7 UseDone: the Use/UseWithTarget completion signal (WeenieError code).</summary>
public static uint? ParseUseDone(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x019A InventoryPutObjectIn3D: server dropped item to ground.</summary>
public static uint? ParsePutObjectIn3D(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x00A0 InventoryServerSaveFailed: revert a speculative local inventory op.</summary>
public static uint? ParseInventoryServerSaveFailed(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x0052 CloseGroundContainer: server closed a ground container view.</summary>
public static uint? ParseCloseGroundContainer(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x0207 TradeFailure: server trade error code.</summary>
public static uint? ParseTradeFailure(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x0200 AddToTrade: (itemGuid, slotIndex).</summary>
public readonly record struct AddToTrade(uint ItemGuid, uint SlotIndex);
public static AddToTrade? ParseAddToTrade(ReadOnlySpan<byte> payload)
{
if (payload.Length < 8) return null;
return new AddToTrade(
BinaryPrimitives.ReadUInt32LittleEndian(payload),
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(4)));
}
/// <summary>0x0202 AcceptTrade: initiator guid.</summary>
public static uint? ParseAcceptTrade(ReadOnlySpan<byte> payload)
{
if (payload.Length < 4) return null;
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
}
/// <summary>0x0264 QueryItemManaResponse: (itemGuid, manaPercent).</summary>
public readonly record struct QueryItemManaResponse(uint ItemGuid, float ManaPercent);
public static QueryItemManaResponse? ParseQueryItemManaResponse(ReadOnlySpan<byte> payload)
{
if (payload.Length < 8) return null;
return new QueryItemManaResponse(
BinaryPrimitives.ReadUInt32LittleEndian(payload),
BinaryPrimitives.ReadSingleLittleEndian(payload.Slice(4)));
}
/// <summary>0x0274 CharacterConfirmationRequest — server-driven modal confirm.</summary>
public readonly record struct CharacterConfirmationRequest(
uint Type,
uint ContextId,
uint OtherGuid,
string Message);
public static CharacterConfirmationRequest? ParseCharacterConfirmationRequest(ReadOnlySpan<byte> payload)
{
if (payload.Length < 12) return null;
int pos = 0;
uint type = BinaryPrimitives.ReadUInt32LittleEndian(payload); pos += 4;
uint contextId = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(pos)); pos += 4;
uint otherGuid = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(pos)); pos += 4;
try
{
string msg = ReadString16L(payload, ref pos);
return new CharacterConfirmationRequest(type, contextId, otherGuid, msg);
}
catch { return null; }
}
// ── Shared string reader (matches LoginRequest.ReadString16L) ───────────
private static string ReadString16L(ReadOnlySpan<byte> source, ref int pos)