fix(ui): port live experience quality updates (#217)
Parse retail PrivateUpdatePropertyInt64 and route authoritative Total/Available Experience through both local-player projections so Attributes, the level meter, and Skills refresh together. Preserve the existing retail XP curve and right-align the Total XP value. Close the user-confirmed item-give gate for #216 and record the named-retail/ACE/holtburger conformance evidence. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
30d294506c
commit
84b7d2d7cd
15 changed files with 391 additions and 29 deletions
35
src/AcDream.Core.Net/Messages/PrivateUpdatePropertyInt64.cs
Normal file
35
src/AcDream.Core.Net/Messages/PrivateUpdatePropertyInt64.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Buffers.Binary;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Inbound retail <c>PrivateUpdatePropertyInt64 (0x02CF)</c>: one signed
|
||||
/// 64-bit quality update for the local player. Total Experience (quality 1)
|
||||
/// and Available Experience (quality 2) use this message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail: <c>CM_Qualities::DispatchUI_PrivateUpdateInt64 @ 0x006AEAD0</c>
|
||||
/// reads the sequence byte at +4, property at +5, and value at +9, then calls
|
||||
/// <c>ClientObjMaintSystem::Handle_Qualities__PrivateUpdateInt64 @ 0x00559000</c>.
|
||||
/// ACE <c>GameMessagePrivateUpdatePropertyInt64</c> and holtburger's
|
||||
/// <c>PrivateUpdatePropertyInt64Data</c> confirm the same field order.
|
||||
/// </remarks>
|
||||
public static class PrivateUpdatePropertyInt64
|
||||
{
|
||||
public const uint Opcode = 0x02CFu;
|
||||
public const int BodySize = 17; // opcode(4) + sequence(1) + property(4) + value(8)
|
||||
|
||||
public readonly record struct Parsed(uint Property, long Value);
|
||||
|
||||
/// <summary>Parse a complete 0x02CF body, or return null on mismatch/truncation.</summary>
|
||||
public static Parsed? TryParse(ReadOnlySpan<byte> body)
|
||||
{
|
||||
if (body.Length < BodySize) return null;
|
||||
if (BinaryPrimitives.ReadUInt32LittleEndian(body) != Opcode) return null;
|
||||
|
||||
const int propertyOffset = 5; // opcode + one-byte quality sequence
|
||||
uint property = BinaryPrimitives.ReadUInt32LittleEndian(body[propertyOffset..]);
|
||||
long value = BinaryPrimitives.ReadInt64LittleEndian(body[(propertyOffset + 4)..]);
|
||||
return new Parsed(property, value);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue