ea108e48e4
B1 SEPARATION: pairwise soft-collision pass INSIDE EnemyAISystem (sole enemy-Position writer preserved; O(n^2) fine at MaxAlive<=14). Knocked/ lunging enemies keep committed motion but still push neighbours; the boss is never pushed; enemies yield a small personal radius around players (below melee range per review B1-1); displacement goes through the swept move so nothing shoves through walls. SeparationMaxSpeed = live knob. B2 POISE: the windup-cancel lived in the knockback CONTINUE branches (grunt/ charger/spitter). Threshold on the EXISTING KnockbackState.Speed channel: light melee (6) nudges without interrupting; the finisher (10.8) and cone (8) stagger as before. StaggerKnockbackSpeed = live knob (default 7). B4 BOSS LUNGE: a telegraphed gap-closer on its own cooldown when the target sits outside slam reach (repositioning - the slam stays the damage beat). BossState.PendingAttack (server-only byte) disambiguates the shared windup elapse (review-confirmed: naive reuse would SLAM on a lunge elapse); LungeState.UntilTick spans windup+travel so the existing IsLunging ghost bit replicates the tell, and the client draws a travel wedge instead of the slam ring while it is set. B5 PARTY HP SCALE: boss Health x(1 + 0.75/extra LIVING expedition player) at spawn (not RunParticipant - dead-respawned members park at base). Max is a GhostField so the bar stays truthful. B6 RUN-FAILED BANNER: silent wipes used to land players home with zero explanation. Client detects the (in-run)->Staging edge with a launch-cached Charge (NEVER Returning - a 1-tick transient that precedes the bank by a tick) and shows EXPEDITION FAILED for 6 s. B7 FIRE ANIM: the class ability finally moves the body - a stateless window from replicated AbilityCooldown.NextFireTick minus derived CooldownTicks (works for predicted local + interpolated remotes, no cached edges). 456/456 EditMode; live Play smoke: launch -> 5 kills credit the room clear through the corpse window -> boon window opens -> corpses expire clean, no exceptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 lines
2.5 KiB
C#
41 lines
2.5 KiB
C#
using Unity.Entities;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// SERVER-ONLY working state for the expedition BOSS (a scaled Charger that <see cref="Server"/>'s
|
|
/// RoomEnemyDirectorSystem tags at spawn). NOT replicated and NOT baked — added at runtime via ECB on the boss
|
|
/// entity, so it needs no ghost-hash change (a runtime-added replicated component would not replicate anyway;
|
|
/// this one is deliberately server-only, like <see cref="LungeState"/>/<see cref="KnockbackState"/>).
|
|
/// <para>
|
|
/// Component PRESENCE is the boss discriminator: BossAISystem is the SOLE mover/attacker of
|
|
/// <c>.WithAll<EnemyTag, BossState>()</c>, and EnemyAISystem's Charger MOVE pass excludes it via
|
|
/// <c>.WithNone<BossState>()</c> so exactly one system writes the boss's Position/AttackWindup. The boss does
|
|
/// NOT use LungeState (its signature move is a telegraphed radial SLAM, not a lunge) — so EnemyAISystem's
|
|
/// IsLunging derive visits it but sees <c>LungeState.UntilTick==0</c> and derives the bit off (harmless single
|
|
/// writer). <see cref="Phase"/> is a byte (never a C# enum on a Bursted path — the cross-assembly-enum ICE rule).
|
|
/// All tick fields route through <c>TickUtil.NonZero</c> and compare with <see cref="Unity.NetCode.NetworkTick"/>.
|
|
/// </para>
|
|
/// </summary>
|
|
public struct BossState : IComponentData
|
|
{
|
|
/// <summary>1 = phase one (heavy Charger + slam), 2 = phase two (<50% HP: faster + summons adds). Byte, not enum.</summary>
|
|
public byte Phase;
|
|
|
|
/// <summary>Earliest raw tick the boss may begin its next radial SLAM wind-up (NonZero; 0 = ready).</summary>
|
|
public uint SlamReadyTick;
|
|
|
|
/// <summary>Earliest raw tick the boss may summon its next add pack (phase two only; NonZero; 0 = ready).</summary>
|
|
public uint SummonReadyTick;
|
|
|
|
/// <summary>Earliest raw tick the boss may begin its next LUNGE wind-up (B4; NonZero; 0 = ready).</summary>
|
|
public uint LungeReadyTick;
|
|
|
|
/// <summary>Which attack the live AttackWindup belongs to: 0 = radial slam, 1 = lunge (B4 — slam and lunge
|
|
/// share the one replicated windup field; this server-only byte disambiguates the elapse branch). The client
|
|
/// distinguishes via the IsLunging ghost bit instead (BossAISystem holds LungeState.UntilTick through the
|
|
/// lunge wind-up + travel, so EnemyAISystem's derive turns the bit on).</summary>
|
|
public byte PendingAttack;
|
|
}
|
|
}
|