f3eccec524
Four playtest do-now wins: - Enemy health bars: pooled world-space Canvas, on-damage-sticky + fade, always-on <25% HP (CombatFeedbackSystem; no new replication). - Telegraph fix: new baked client-safe EnemyTelegraph sizes the danger-cone ramp per enemy (0->1 ending at impact, fixes the Charger plateau); windup 18->22; a windup scale-pulse. - Build-mode toggle: BuildPaletteState.PaletteOpen hides the palette by default, Tab / gamepad-Y toggles, with a discovery chip (HudSystem/BuildSendSystem). - Charger committed-lunge tell: [GhostEnabledBit] IsLunging derived once/tick from LungeState (the Dead idiom); the danger cone persists through the lunge. 345/345 EditMode (+3 IsLunging derive tests); Play-validated: ghost-hash change did not break the handshake, bake correct (telegraph on all enemies, IsLunging baked-disabled on the Charger, replicated to client), no runtime errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
3.2 KiB
C#
50 lines
3.2 KiB
C#
using Unity.Entities;
|
|
using Unity.Mathematics;
|
|
using Unity.NetCode;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// MC-1 — server-only Charger lunge state (a KnockbackState SHAPE-twin). Component PRESENCE is the Charger
|
|
/// discriminator (no enum / brain byte — honours the Burst cross-assembly-enum rule; EnemyAISystem is Bursted):
|
|
/// a Husk variant baked with LungeState is driven by the Charger branch, every other Husk by the Grunt branch
|
|
/// (which excludes these via <c>.WithNone<LungeState>()</c>). On a wind-up commit the Charger LOCKS
|
|
/// <see cref="Dir"/> toward the target and travels at <see cref="Speed"/> until <see cref="UntilTick"/> — dealing
|
|
/// contact damage if it connects, or staggering into a punish window if it whiffs (wall-stop or overshoot).
|
|
/// NOT a <c>[GhostField]</c> (the lunged position replicates via the stock LocalTransform variant, like
|
|
/// KnockbackState). All ticks via <c>TickUtil.NonZero</c>; compared with <see cref="Unity.NetCode.NetworkTick"/> only.
|
|
/// </summary>
|
|
public struct LungeState : IComponentData
|
|
{
|
|
/// <summary>Fixed planar lunge heading, locked at commit (world XZ -> float2 x,y).</summary>
|
|
public float2 Dir;
|
|
|
|
/// <summary>Lunge speed (world units/s); only meaningful while <see cref="UntilTick"/> is active.</summary>
|
|
public float Speed;
|
|
|
|
/// <summary>Raw tick the lunge ends (NonZero). <c>0</c> = not lunging. Active while .IsNewerThan(serverTick).</summary>
|
|
public uint UntilTick;
|
|
|
|
/// <summary>Raw tick the whiff-stagger punish window ends (NonZero; set at BOTH whiff sites). 0 = not
|
|
/// staggered — or already punished: HealthApplyDamageSystem zeroes it when the first player-sourced hit
|
|
/// lands so a window counts ONCE in DevTelemetry.ChargerWhiffPunishesLanded. The attack lockout itself
|
|
/// rides EnemyAttackCooldown.NextAttackTick; this field only scores the punish.</summary>
|
|
public uint StaggerUntilTick;
|
|
}
|
|
|
|
/// <summary>
|
|
/// REPLICATED enableable MID-LUNGE flag on a Charger (Slice 1, Feature D). ENABLED for exactly the ticks a
|
|
/// Charger is committed to its locked-direction lunge (<see cref="LungeState.UntilTick"/> active), DISABLED
|
|
/// otherwise. The ONLY replicated Charger surface beyond the stock LocalTransform — a <c>[GhostEnabledBit]</c>,
|
|
/// NOT a [GhostField], because the client needs only on/off: the lunge HEADING is already carried by the
|
|
/// replicated LocalTransform.Rotation (EnemyAISystem writes LookRotationSafe(lungeDir) each lunge tick), so the
|
|
/// client indicator derives direction via AnimParamMath.PlanarForward like the danger cone already does. Fixes
|
|
/// the cue VANISHING at commit (AttackWindup zeroes on commit, so a windup-gated cone disappears exactly when
|
|
/// the danger is realest): this bit STAYS on through the committed travel. Server-derived once per tick from
|
|
/// LungeState.UntilTick in EnemyAISystem (the sole LungeState writer); BAKE DISABLED (a Charger spawns
|
|
/// not-lunging) + visit via .WithPresent<IsLunging>() to write the bit while disabled (the Dead idiom).
|
|
/// </summary>
|
|
[GhostEnabledBit]
|
|
public struct IsLunging : IComponentData, IEnableableComponent { }
|
|
}
|