fix(net): Phase L.1c conform combat wire events

This commit is contained in:
Erik 2026-04-28 10:54:50 +02:00
parent 460f95cb42
commit 29afc94b94
8 changed files with 241 additions and 177 deletions

View file

@ -3,60 +3,79 @@ using System.Buffers.Binary;
namespace AcDream.Core.Net.Messages;
/// <summary>
/// Outbound <c>0x0008 AttackTargetRequest</c> GameAction.
/// Outbound combat attack GameActions.
///
/// Retail/ACE use distinct payloads for melee and missile:
///
/// <para>
/// Wire layout (inside the <c>0xF7B1</c> GameAction envelope):
/// <code>
/// u32 0xF7B1 // GameAction envelope opcode
/// u32 gameActionSequence // client sequence
/// u32 0x0008 // sub-opcode
/// u32 targetGuid // who to attack
/// f32 powerLevel // [0.0, 1.0] — the power bar position
/// f32 accuracyLevel // [0.0, 1.0] — for missile weapons
/// u32 0x0008 // TargetedMeleeAttack
/// u32 targetGuid
/// u32 attackHeight // 1=High, 2=Medium, 3=Low
/// f32 powerLevel // [0.0, 1.0]
///
/// u32 0xF7B1
/// u32 gameActionSequence
/// u32 0x000A // TargetedMissileAttack
/// u32 targetGuid
/// u32 attackHeight
/// f32 accuracyLevel // [0.0, 1.0]
/// </code>
/// </para>
///
/// <para>
/// The server ALREADY knows the attacker (it's the session's player),
/// so this message only carries the target + attack params. The server
/// then rolls damage, picks a body part, and broadcasts
/// <see cref="GameEventType.VictimNotification"/> / AttackerNotification
/// / DefenderNotification / EvasionAttackerNotification /
/// EvasionDefenderNotification with the result.
/// </para>
///
/// <para>
/// References: r02 §7 (wire format), r08 §3 opcode 0x0008.
/// </para>
/// References: CM_Combat::Event_TargetedMeleeAttack 0x006A9C10,
/// CM_Combat::Event_TargetedMissileAttack 0x006A9D60, ACE
/// GameActionTargetedMeleeAttack/GameActionTargetedMissileAttack, and
/// holtburger protocol game_action.rs.
/// </summary>
public static class AttackTargetRequest
{
public const uint GameActionEnvelope = 0xF7B1u;
public const uint SubOpcode = 0x0008u;
public const uint TargetedMeleeAttackOpcode = 0x0008u;
public const uint TargetedMissileAttackOpcode = 0x000Au;
public const uint CancelAttackOpcode = 0x01B7u;
/// <summary>
/// Build the wire body for an attack request.
/// </summary>
/// <param name="powerLevel">[0..1] melee power bar position.</param>
/// <param name="accuracyLevel">[0..1] missile accuracy bar position; pass 0 for melee.</param>
/// <param name="attackHeight">1=High, 2=Medium, 3=Low.</param>
public static byte[] Build(
/// <summary>Build the wire body for a targeted melee attack.</summary>
public static byte[] BuildMelee(
uint gameActionSequence,
uint targetGuid,
float powerLevel,
float accuracyLevel,
uint attackHeight)
uint attackHeight,
float powerLevel)
{
byte[] body = new byte[28];
byte[] body = new byte[24];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), gameActionSequence);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), SubOpcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), TargetedMeleeAttackOpcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), targetGuid);
BinaryPrimitives.WriteSingleLittleEndian(body.AsSpan(16), powerLevel);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), attackHeight);
BinaryPrimitives.WriteSingleLittleEndian(body.AsSpan(20), powerLevel);
return body;
}
/// <summary>Build the wire body for a targeted missile attack.</summary>
public static byte[] BuildMissile(
uint gameActionSequence,
uint targetGuid,
uint attackHeight,
float accuracyLevel)
{
byte[] body = new byte[24];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), gameActionSequence);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), TargetedMissileAttackOpcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), targetGuid);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), attackHeight);
BinaryPrimitives.WriteSingleLittleEndian(body.AsSpan(20), accuracyLevel);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(24), attackHeight);
return body;
}
/// <summary>Build the wire body for cancelling an active attack request.</summary>
public static byte[] BuildCancel(uint gameActionSequence)
{
byte[] body = new byte[12];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), gameActionSequence);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), CancelAttackOpcode);
return body;
}
}