Phase 1 B1/B2/B4-B7: separation, poise, boss lunge, party HP scale, run-failed banner, fire anim
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>
This commit is contained in:
@@ -113,6 +113,20 @@ namespace ProjectM.Client
|
||||
return start.IsValid && end.IsValid && !start.IsNewerThan(serverTick) && end.IsNewerThan(serverTick);
|
||||
}
|
||||
|
||||
// B7: the class FIRE ability finally plays a body animation - a stateless window derived from the
|
||||
// replicated AbilityCooldown ([GhostField] NextFireTick) minus the locally-derived CooldownTicks
|
||||
// (EffectiveAbilityStats recomputes identically on every world), so it works for the predicted local
|
||||
// player AND interpolated remotes with no cached edges (review B7: edge-caches false-fire on
|
||||
// relevancy/join/rollback). Reuses the swing clip until a dedicated fire clip exists.
|
||||
static bool FireActive(uint nextFireRaw, int cooldownTicks, NetworkTick serverTick, uint animTicks)
|
||||
{
|
||||
if (nextFireRaw == 0u || cooldownTicks <= 0 || !serverTick.IsValid) return false;
|
||||
uint startRaw = TickUtil.NonZero(nextFireRaw - (uint)cooldownTicks);
|
||||
var start = new NetworkTick(startRaw);
|
||||
var end = new NetworkTick(TickUtil.NonZero(startRaw + animTicks));
|
||||
return start.IsValid && end.IsValid && !start.IsNewerThan(serverTick) && end.IsNewerThan(serverTick);
|
||||
}
|
||||
|
||||
// LOCAL: GhostOwnerIsLocal ENABLED -> exactly the owned player. WithPresent<Dead> so alive
|
||||
// (Dead-disabled) players are visited. NOTE: GhostOwnerIsLocal as a WithAll filter respects the
|
||||
// enable bit; do NOT take it as an `in` parameter (that matches on presence -> drives remotes too).
|
||||
@@ -132,12 +146,16 @@ namespace ProjectM.Client
|
||||
in EffectiveCharacterStats stats,
|
||||
in KinematicCharacterBody body,
|
||||
in MeleeCombo melee,
|
||||
in AbilityCooldown fireCooldown,
|
||||
in EffectiveAbilityStats abilityStats,
|
||||
EnabledRefRO<Dead> dead)
|
||||
{
|
||||
var a = new AnimatorParametersAspect(parametersArr, indexTable);
|
||||
float3 p = AnimParamMath.LocomotionParams(body.RelativeVelocity, facing.Direction, stats.MoveSpeed);
|
||||
Write(ref a, p, dead.ValueRO, moveX, moveZ, speed, isDead);
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking, SwingActive(melee, serverTick, attackTicks));
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking,
|
||||
SwingActive(melee, serverTick, attackTicks)
|
||||
|| FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +181,8 @@ namespace ProjectM.Client
|
||||
in PlayerFacing facing,
|
||||
in EffectiveCharacterStats stats,
|
||||
in MeleeCombo melee,
|
||||
in AbilityCooldown fireCooldown,
|
||||
in EffectiveAbilityStats abilityStats,
|
||||
EnabledRefRO<Dead> dead)
|
||||
{
|
||||
seen.Add(e);
|
||||
@@ -175,7 +195,9 @@ namespace ProjectM.Client
|
||||
var a = new AnimatorParametersAspect(parametersArr, indexTable);
|
||||
float3 p = AnimParamMath.LocomotionParams(vel, facing.Direction, stats.MoveSpeed);
|
||||
Write(ref a, p, dead.ValueRO, moveX, moveZ, speed, isDead);
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking, SwingActive(melee, serverTick, attackTicks));
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking,
|
||||
SwingActive(melee, serverTick, attackTicks)
|
||||
|| FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user