feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
7b7ffcd278
commit
07be994d97
84 changed files with 17822 additions and 1051 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
|
@ -271,14 +272,19 @@ public static class GameEvents
|
|||
/// <summary>
|
||||
/// 0x02C3 MagicRemoveEnchantment — (layerId, spellId).
|
||||
/// </summary>
|
||||
public readonly record struct MagicRemoveEnchantment(uint LayerId, uint SpellId);
|
||||
public readonly record struct LayeredSpellId(ushort SpellId, ushort Layer)
|
||||
{
|
||||
public uint Packed => SpellId | ((uint)Layer << 16);
|
||||
}
|
||||
|
||||
public readonly record struct MagicRemoveEnchantment(ushort SpellId, ushort Layer);
|
||||
|
||||
public static MagicRemoveEnchantment? ParseMagicRemoveEnchantment(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.Length < 8) return null;
|
||||
if (payload.Length < 4) return null;
|
||||
return new MagicRemoveEnchantment(
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(4)));
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(payload),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(2)));
|
||||
}
|
||||
|
||||
/// <summary>0x01A8 MagicRemoveSpell — spell id removed from spellbook.</summary>
|
||||
|
|
@ -294,26 +300,20 @@ public static class GameEvents
|
|||
/// stat mods. We expose the first few fields that drive the enchant
|
||||
/// bar UI; the rest is available via the raw payload view.
|
||||
/// </summary>
|
||||
public readonly record struct EnchantmentSummary(
|
||||
uint SpellId,
|
||||
uint LayerId,
|
||||
float Duration,
|
||||
uint CasterGuid);
|
||||
|
||||
public static EnchantmentSummary? ParseMagicUpdateEnchantment(ReadOnlySpan<byte> payload)
|
||||
public static PlayerDescriptionParser.EnchantmentEntry? ParseMagicUpdateEnchantment(
|
||||
ReadOnlySpan<byte> payload)
|
||||
{
|
||||
// Layout (ACE Enchantment.Pack):
|
||||
// u32 spellId
|
||||
// u32 layerId
|
||||
// f32 duration
|
||||
// u32 casterGuid
|
||||
// ... (stat mods, category, power, etc.)
|
||||
if (payload.Length < 16) return null;
|
||||
return new EnchantmentSummary(
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(4)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(payload.Slice(8)),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(12)));
|
||||
int position = 0;
|
||||
try { return EnchantmentWireReader.Read(payload, ref position); }
|
||||
catch (FormatException) { return null; }
|
||||
}
|
||||
|
||||
public static IReadOnlyList<PlayerDescriptionParser.EnchantmentEntry>?
|
||||
ParseMagicUpdateMultipleEnchantments(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
int position = 0;
|
||||
try { return EnchantmentWireReader.ReadList(payload, ref position); }
|
||||
catch (FormatException) { return null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -323,6 +323,23 @@ public static class GameEvents
|
|||
public static MagicRemoveEnchantment? ParseMagicDispelEnchantment(ReadOnlySpan<byte> payload)
|
||||
=> ParseMagicRemoveEnchantment(payload);
|
||||
|
||||
public static IReadOnlyList<LayeredSpellId>? ParseMagicLayeredSpellList(
|
||||
ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.Length < 4) return null;
|
||||
uint count = BinaryPrimitives.ReadUInt32LittleEndian(payload);
|
||||
if (count > 0x4000 || payload.Length - 4 < checked((int)count * 4)) return null;
|
||||
var result = new LayeredSpellId[count];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
int offset = 4 + i * 4;
|
||||
result[i] = new LayeredSpellId(
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(offset, 2)),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(offset + 2, 2)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Appraise / identify ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>0x00C9 IdentifyObjectResponse header.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue