using System.Buffers.Binary; namespace AcDream.Core.Net.Messages; /// /// Outbound character-progression + mode-change GameActions. Grouped /// together because they share the same 0xF7B1 envelope + /// single-opcode dispatch pattern and all target the session's player /// (no target guid field). /// /// /// References: r08 §3 rows 0x0044 / 0x0045 / 0x0046 / 0x0047 / 0x0053. /// /// 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, } /// Spend XP to raise an attribute (Strength, Endurance, etc). public static byte[] BuildRaiseAttribute(uint seq, uint attrId, ulong xpSpent) => BuildAttrOrVital(seq, RaiseAttributeOpcode, attrId, xpSpent); /// Spend XP to raise a vital (Health, Stamina, Mana). public static byte[] BuildRaiseVital(uint seq, uint vitalId, ulong xpSpent) => BuildAttrOrVital(seq, RaiseVitalOpcode, vitalId, xpSpent); /// Spend XP to raise a skill. public static byte[] BuildRaiseSkill(uint seq, uint skillId, ulong xpSpent) => BuildAttrOrVital(seq, RaiseSkillOpcode, skillId, xpSpent); /// Spend skill credits to train (unlock) a skill. 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; } /// /// 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. /// 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; } }