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>
36 lines
2.1 KiB
C#
36 lines
2.1 KiB
C#
using Unity.Entities;
|
|
using Unity.NetCode;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// Replicated Husk attack-telegraph signal. While <see cref="WindUpUntilTick"/> is non-zero the Husk is
|
|
/// "winding up" to strike; EnemyAISystem sets it ~<see cref="Tuning.AttackWindupTicks"/> before the strike
|
|
/// lands, and the strike fires when the tick elapses. This is a [GhostField] (the only replicated Husk field
|
|
/// beyond the stock LocalTransform) so the CLIENT can play a ~0.3s pre-strike cue — the client has none of the
|
|
/// server-only timing inputs (EnemyStats / EnemyAttackCooldown), so the wind-up MUST be replicated. A uint tick
|
|
/// (not a [GhostEnabledBit]) so the cue can ramp/countdown and survive a missed snapshot (absolute, not an edge).
|
|
/// </summary>
|
|
public struct AttackWindup : IComponentData
|
|
{
|
|
/// <summary>Server tick the wind-up completes + the strike lands (0 = not winding up; scheduled via TickUtil.NonZero).</summary>
|
|
[GhostField] public uint WindUpUntilTick;
|
|
}
|
|
|
|
/// <summary>Client-safe BAKED telegraph metadata (NOT a [GhostField] — baked values are identical on
|
|
/// both worlds, so the client reads it directly to size the danger-cone ramp + pick the Charger look).
|
|
/// WindupTicks = the variant's wind-up lead in ticks (the client danger-ramp denominator: Grunt =
|
|
/// Tuning.AttackWindupTicks, Charger = ChargerWindupTicks); IsCharger distinguishes the variant since
|
|
/// LungeState is server-only. Both stored as byte (no enum on a client-read path — honours the Burst
|
|
/// byte-not-enum convention); never changes at runtime, so a missed snapshot is irrelevant. Baked by
|
|
/// EnemyAuthoring.EnemyBaker (the SOLE writer — reads sibling ChargerAuthoring to set the Charger values).</summary>
|
|
public struct EnemyTelegraph : IComponentData
|
|
{
|
|
/// <summary>Per-variant wind-up DURATION in ticks (the client danger-ramp denominator).</summary>
|
|
public byte WindupTicks;
|
|
|
|
/// <summary>0 = Grunt-style; 1 = Charger (committed-lunge tell).</summary>
|
|
public byte IsCharger;
|
|
}
|
|
}
|