feat(char): character progression actions — Raise / Train / CombatMode
Outbound GameActions for XP-spending + combat-mode-change. These complete the wire surface for the character-sheet UI: the player clicks "spend XP on Strength," the panel calls BuildRaiseAttribute, the session sends it, the server responds with updated PlayerDescription or PrivateUpdateAttribute GameEvents. Wire layer: - BuildRaiseAttribute (0x0045): attrId u32, xpSpent u64. - BuildRaiseVital (0x0044): vitalId u32, xpSpent u64. - BuildRaiseSkill (0x0046): skillId u32, xpSpent u64. - BuildTrainSkill (0x0047): skillId u32, credits u32 (note: credits is u32 here, NOT u64 like the xpSpent variants). - BuildChangeCombatMode (0x0053): mode enum as u32 (Undef=0, NonCombat=1, Melee=2, Missile=3, Magic=4, Peaceful=5). Tests (5 new): byte-exact encoding of each, including the Train/ Raise size difference due to u32 vs u64 payloads. Build green, 621 tests pass (up from 616). Ref: r08 §3 rows 0x0044 / 0x0045 / 0x0046 / 0x0047 / 0x0053. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
68efb60b49
commit
d461279207
2 changed files with 139 additions and 0 deletions
79
src/AcDream.Core.Net/Messages/CharacterActions.cs
Normal file
79
src/AcDream.Core.Net/Messages/CharacterActions.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Buffers.Binary;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Outbound character-progression + mode-change GameActions. Grouped
|
||||
/// together because they share the same <c>0xF7B1</c> envelope +
|
||||
/// single-opcode dispatch pattern and all target the session's player
|
||||
/// (no target guid field).
|
||||
///
|
||||
/// <para>
|
||||
/// References: r08 §3 rows 0x0044 / 0x0045 / 0x0046 / 0x0047 / 0x0053.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class CharacterActions
|
||||
{
|
||||
public const uint GameActionEnvelope = 0xF7B1u;
|
||||
|
||||
public const uint RaiseAttributeOpcode = 0x0045u; // u32 attr, u64 xpSpent
|
||||
public const uint RaiseVitalOpcode = 0x0044u; // u32 vital, u64 xpSpent
|
||||
public const uint RaiseSkillOpcode = 0x0046u; // u32 skillId, u64 xpSpent
|
||||
public const uint TrainSkillOpcode = 0x0047u; // u32 skillId, u32 credits
|
||||
public const uint ChangeCombatModeOpcode = 0x0053u; // u32 combatMode
|
||||
|
||||
public enum CombatMode : uint
|
||||
{
|
||||
Undef = 0, NonCombat = 1, Melee = 2, Missile = 3, Magic = 4, Peaceful = 5,
|
||||
}
|
||||
|
||||
/// <summary>Spend XP to raise an attribute (Strength, Endurance, etc).</summary>
|
||||
public static byte[] BuildRaiseAttribute(uint seq, uint attrId, ulong xpSpent)
|
||||
=> BuildAttrOrVital(seq, RaiseAttributeOpcode, attrId, xpSpent);
|
||||
|
||||
/// <summary>Spend XP to raise a vital (Health, Stamina, Mana).</summary>
|
||||
public static byte[] BuildRaiseVital(uint seq, uint vitalId, ulong xpSpent)
|
||||
=> BuildAttrOrVital(seq, RaiseVitalOpcode, vitalId, xpSpent);
|
||||
|
||||
/// <summary>Spend XP to raise a skill.</summary>
|
||||
public static byte[] BuildRaiseSkill(uint seq, uint skillId, ulong xpSpent)
|
||||
=> BuildAttrOrVital(seq, RaiseSkillOpcode, skillId, xpSpent);
|
||||
|
||||
/// <summary>Spend skill credits to train (unlock) a skill.</summary>
|
||||
public static byte[] BuildTrainSkill(uint seq, uint skillId, uint credits)
|
||||
{
|
||||
byte[] body = new byte[20];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), TrainSkillOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), skillId);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), credits);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change combat mode. Peace ↔ Melee / Missile / Magic toggles that
|
||||
/// switch stance + weapon ready-state. The animation + weapon
|
||||
/// wield broadcasts are server-driven from this message.
|
||||
/// </summary>
|
||||
public static byte[] BuildChangeCombatMode(uint seq, CombatMode mode)
|
||||
{
|
||||
byte[] body = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), ChangeCombatModeOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), (uint)mode);
|
||||
return body;
|
||||
}
|
||||
|
||||
private static byte[] BuildAttrOrVital(uint seq, uint sub, uint id, ulong xp)
|
||||
{
|
||||
byte[] body = new byte[24];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), sub);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), id);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(16), xp);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue