feat(combat): Phase E.4 AttackTargetRequest + combat notification pipeline
Completes the client-side combat loop: send attacks, receive server's damage broadcasts, maintain per-entity health state for HP bars + damage floaters. All atop Phase F.1's GameEvent dispatcher. Wire layer: - AttackTargetRequest (0x0008 C→S, inside 0xF7B1): targetGuid + powerLevel + accuracyLevel + attackHeight. 28-byte body. - GameEvents parsers for all combat notifications from r08 §4: - VictimNotification (0x01AC) — you got hit, full details - KillerNotification (0x01AD) — you killed X - AttackerNotification (0x01B1) — you hit X for Y (damage%) - DefenderNotification (0x01B2) — X hit you - EvasionAttackerNotification (0x01B3) — X evaded - EvasionDefenderNotification (0x01B4) — you evaded X - AttackDone (0x01A7) — attack sequence completed Core layer: - CombatState: per-entity health-percent cache + typed events (HealthChanged, DamageTaken, DamageDealtAccepted, EvadedIncoming, MissedOutgoing, AttackDone). Each event carries enough detail for the UI to render damage floaters, HP bars, and a combat log panel. Server is authoritative; client only mirrors state. The server computes damage (armor, resist, crit, hit-chance); the client only displays results. Predictive UI like "estimated damage at 0.75 power" still works via the existing CombatMath helper class that was in the scaffold (r02 §5 formulas). Tests (13 new): - AttackTargetRequest byte-exact wire encoding - VictimNotification / AttackerNotification / EvasionAttacker / AttackDone round-trip parse. - CombatState: UpdateHealth caches + fires, Victim fires DamageTaken, Attacker fires DamageDealt, Evasion routes to right event, AttackDone carries sequence+error, Clear resets cache. Build green, 544 tests pass (up from 532). Ref: r02 §7 (wire formats), r08 §4 (event payloads), ACE GameEvent*Notification.cs families. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2561f5599f
commit
2e3f9d7a04
5 changed files with 516 additions and 0 deletions
62
src/AcDream.Core.Net/Messages/AttackTargetRequest.cs
Normal file
62
src/AcDream.Core.Net/Messages/AttackTargetRequest.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System.Buffers.Binary;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Outbound <c>0x0008 AttackTargetRequest</c> GameAction.
|
||||
///
|
||||
/// <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 attackHeight // 1=High, 2=Medium, 3=Low
|
||||
/// </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>
|
||||
/// </summary>
|
||||
public static class AttackTargetRequest
|
||||
{
|
||||
public const uint GameActionEnvelope = 0xF7B1u;
|
||||
public const uint SubOpcode = 0x0008u;
|
||||
|
||||
/// <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(
|
||||
uint gameActionSequence,
|
||||
uint targetGuid,
|
||||
float powerLevel,
|
||||
float accuracyLevel,
|
||||
uint attackHeight)
|
||||
{
|
||||
byte[] body = new byte[28];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), gameActionSequence);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), SubOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), targetGuid);
|
||||
BinaryPrimitives.WriteSingleLittleEndian(body.AsSpan(16), powerLevel);
|
||||
BinaryPrimitives.WriteSingleLittleEndian(body.AsSpan(20), accuracyLevel);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(24), attackHeight);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue