Phase 1 B3: enemies die with a corpse window instead of popping out of existence
Server: HealthApplyDamageSystem marks EnemyTag Dying{UntilTick} (TickUtil.
NonZero, ~54 ticks) on the lethal 0-crossing instead of instant destroy,
zeroes every live cue (replicated AttackWindup, LungeState + IsLunging bit,
KnockbackState), destroys on expiry; plain-world tests keep instant destroy
(no NetworkTime) so the suite's assertions stay meaningful.
Every consumer now ignores corpses (all confirmed entity-count/unfiltered by
the design review): EnemyAISystem all 4 passes, BossAISystem brain + summon
cap, RoomEnemyDirector room-clear + MaxAlive fit, WaveSystem cap + cleared,
CyclePhaseSystem breach wipe + DefendCleared, ThreatDirector timeout cull,
TurretFire targeting, CoreDamage drain, ProjectileDamage snapshot (corpses
are not shields), AbilityFire auto-aim candidates, debug cull + telemetry.
Wipe passes skip Dying to avoid cross-ECB double-destroys.
Client: EnemyAnimationDriveSystem finally drives the controller's IsDead
param (Health.Current<=0 is the replicated death read; corpse locomotion
zeroed); CombatFeedbackSystem moves the kill CRUNCH to the 0-crossing (the
prune-edge timing would land it ~1 s late) - the prune keeps a small
dissolve puff for corpses and the legacy full read for alive-vanish culls.
456/456 EditMode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,13 +132,46 @@ namespace ProjectM.Server
|
||||
|
||||
health.ValueRW.Current = newHp;
|
||||
|
||||
// Server-authoritative death: training dummies + enemies + EB-1 Destructible structures despawn;
|
||||
// Server-authoritative death: training dummies + EB-1 Destructible structures despawn instantly;
|
||||
// player death is deferred (clamp only). A structure carries NO EffectiveCharacterStats, so it took
|
||||
// the math.max(0,..) branch above and CAN reach 0 — never give a structure stats (it would clamp to
|
||||
// a non-zero floor and become immortal).
|
||||
if (health.ValueRO.Current <= 0f && (SystemAPI.HasComponent<TrainingDummyTag>(entity) || SystemAPI.HasComponent<EnemyTag>(entity) || SystemAPI.HasComponent<Destructible>(entity)))
|
||||
ecb.DestroyEntity(entity);
|
||||
// Phase 1 (B3): ENEMIES get a corpse window instead — mark Dying ONCE on the lethal crossing and
|
||||
// zero every live cue (the replicated windup, the lunge state + ghost bit, knockback) so clients
|
||||
// never see a corpse telegraphing. Plain-world EditMode tests have no NetworkTime → keep the old
|
||||
// instant destroy there so the existing suite still means what it asserts.
|
||||
if (health.ValueRO.Current <= 0f)
|
||||
{
|
||||
if (SystemAPI.HasComponent<EnemyTag>(entity) && haveTick && netTime.ServerTick.IsValid)
|
||||
{
|
||||
if (!SystemAPI.HasComponent<Dying>(entity))
|
||||
{
|
||||
ecb.AddComponent(entity, new Dying
|
||||
{
|
||||
UntilTick = TickUtil.NonZero(netTime.ServerTick.TickIndexForValidTick + Tuning.EnemyDeathWindowTicks),
|
||||
});
|
||||
if (SystemAPI.HasComponent<AttackWindup>(entity)) SystemAPI.SetComponent(entity, default(AttackWindup));
|
||||
if (SystemAPI.HasComponent<KnockbackState>(entity)) SystemAPI.SetComponent(entity, default(KnockbackState));
|
||||
if (SystemAPI.HasComponent<LungeState>(entity)) SystemAPI.SetComponent(entity, default(LungeState));
|
||||
if (SystemAPI.HasComponent<IsLunging>(entity)) SystemAPI.SetComponentEnabled<IsLunging>(entity, false);
|
||||
}
|
||||
}
|
||||
else if (SystemAPI.HasComponent<TrainingDummyTag>(entity) || SystemAPI.HasComponent<EnemyTag>(entity) || SystemAPI.HasComponent<Destructible>(entity))
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
}
|
||||
// Phase 1 (B3): corpse expiry — destroy Dying enemies whose window elapsed (at-most-once: the Dying
|
||||
// mark is the only path here, and destruction removes it). NetworkTick compare, never raw uint math.
|
||||
if (haveTick && netTime.ServerTick.IsValid)
|
||||
{
|
||||
foreach (var (dying, entity) in SystemAPI.Query<RefRO<Dying>>().WithEntityAccess())
|
||||
{
|
||||
var until = new NetworkTick(dying.ValueRO.UntilTick);
|
||||
if (until.IsValid && !until.IsNewerThan(netTime.ServerTick))
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if ((negatedThisTick != 0u || punishesThisTick != 0u) && SystemAPI.HasSingleton<DevTelemetry>())
|
||||
{
|
||||
var telem = SystemAPI.GetSingletonRW<DevTelemetry>();
|
||||
|
||||
Reference in New Issue
Block a user