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
115
src/AcDream.Core.Net/Messages/EnchantmentWireReader.cs
Normal file
115
src/AcDream.Core.Net/Messages/EnchantmentWireReader.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>Enchantment::UnPack</c> reader shared by PlayerDescription and
|
||||
/// the 0x02C2/0x02C4 magic update events. The record is 60 bytes, plus the
|
||||
/// optional four-byte spell-set id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail reference: <c>CM_Magic::DispatchUI_UpdateEnchantment</c>
|
||||
/// (0x006A32F0). Layout cross-checked against ACE
|
||||
/// <c>Network/Structure/Enchantment.cs</c>.
|
||||
/// </remarks>
|
||||
public static class EnchantmentWireReader
|
||||
{
|
||||
public static PlayerDescriptionParser.EnchantmentEntry Read(
|
||||
ReadOnlySpan<byte> source,
|
||||
ref int position,
|
||||
PlayerDescriptionParser.EnchantmentBucket bucket = 0)
|
||||
{
|
||||
if (source.Length - position < 60)
|
||||
throw new FormatException("truncated enchantment record");
|
||||
|
||||
ushort spellId = ReadU16(source, ref position);
|
||||
ushort layer = ReadU16(source, ref position);
|
||||
ushort spellCategory = ReadU16(source, ref position);
|
||||
ushort hasSpellSetId = ReadU16(source, ref position);
|
||||
uint powerLevel = ReadU32(source, ref position);
|
||||
double startTime = ReadF64(source, ref position);
|
||||
double duration = ReadF64(source, ref position);
|
||||
uint casterGuid = ReadU32(source, ref position);
|
||||
float degradeModifier = ReadF32(source, ref position);
|
||||
float degradeLimit = ReadF32(source, ref position);
|
||||
double lastDegraded = ReadF64(source, ref position);
|
||||
uint statModType = ReadU32(source, ref position);
|
||||
uint statModKey = ReadU32(source, ref position);
|
||||
float statModValue = ReadF32(source, ref position);
|
||||
uint? spellSetId = hasSpellSetId != 0 ? ReadU32(source, ref position) : null;
|
||||
|
||||
if (!double.IsFinite(startTime)
|
||||
|| !double.IsFinite(duration)
|
||||
|| !float.IsFinite(degradeModifier)
|
||||
|| !float.IsFinite(degradeLimit)
|
||||
|| !double.IsFinite(lastDegraded)
|
||||
|| !float.IsFinite(statModValue))
|
||||
{
|
||||
throw new FormatException("non-finite enchantment value");
|
||||
}
|
||||
|
||||
return new PlayerDescriptionParser.EnchantmentEntry(
|
||||
spellId, layer, spellCategory, hasSpellSetId, powerLevel,
|
||||
startTime, duration, casterGuid, degradeModifier, degradeLimit,
|
||||
lastDegraded, statModType, statModKey, statModValue, spellSetId,
|
||||
bucket);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<PlayerDescriptionParser.EnchantmentEntry> ReadList(
|
||||
ReadOnlySpan<byte> source,
|
||||
ref int position,
|
||||
PlayerDescriptionParser.EnchantmentBucket bucket = 0)
|
||||
{
|
||||
if (source.Length - position < 4)
|
||||
throw new FormatException("truncated enchantment list count");
|
||||
|
||||
uint count = ReadU32(source, ref position);
|
||||
if (count > 0x4000)
|
||||
throw new FormatException("unreasonable enchantment list count");
|
||||
|
||||
var result = new List<PlayerDescriptionParser.EnchantmentEntry>((int)count);
|
||||
for (uint i = 0; i < count; i++)
|
||||
result.Add(Read(source, ref position, bucket));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ushort ReadU16(ReadOnlySpan<byte> source, ref int position)
|
||||
{
|
||||
Require(source, position, 2);
|
||||
ushort value = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(position, 2));
|
||||
position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static uint ReadU32(ReadOnlySpan<byte> source, ref int position)
|
||||
{
|
||||
Require(source, position, 4);
|
||||
uint value = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(position, 4));
|
||||
position += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static float ReadF32(ReadOnlySpan<byte> source, ref int position)
|
||||
{
|
||||
Require(source, position, 4);
|
||||
float value = BinaryPrimitives.ReadSingleLittleEndian(source.Slice(position, 4));
|
||||
position += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static double ReadF64(ReadOnlySpan<byte> source, ref int position)
|
||||
{
|
||||
Require(source, position, 8);
|
||||
double value = BinaryPrimitives.ReadDoubleLittleEndian(source.Slice(position, 8));
|
||||
position += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void Require(ReadOnlySpan<byte> source, int position, int count)
|
||||
{
|
||||
if (position < 0 || source.Length - position < count)
|
||||
throw new FormatException("truncated enchantment record");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue