acdream/src/AcDream.Core.Net/Messages/CharacterActions.cs

87 lines
3.7 KiB
C#

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
[Flags]
public enum CombatMode : uint
{
Undef = 0,
NonCombat = 0x01,
Melee = 0x02,
Missile = 0x04,
Magic = 0x08,
ValidCombat = NonCombat | Melee | Missile | Magic,
CombatCombat = Melee | Missile | Magic,
}
/// <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;
}
}