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:
@@ -181,6 +181,28 @@ namespace ProjectM.Client
|
||||
|
||||
// ---- Macro: phase + cycle + countdown (center-top banner) ----
|
||||
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo); // hoisted: the phase banner is lifecycle-aware (Phase 0 fix — it read "AT BASE" inside expedition rooms)
|
||||
|
||||
// B6: run-failed read (review-confirmed design: NEVER key on Returning — it is a 1-tick transient and
|
||||
// the Charge bank lands a tick after it; detect the (in-run)->Staging edge with a launch-cached Charge.
|
||||
// Lifecycle + Charge ride the SAME director ghost snapshot, so at the Staging edge the bank has arrived).
|
||||
if (haveRun)
|
||||
{
|
||||
bool haveGoalNow = SystemAPI.TryGetSingleton<GoalProgress>(out var goalSnap);
|
||||
byte lcNow = runInfo.Lifecycle;
|
||||
if (lcNow == RunLifecycle.Launching && _prevRunLifecycle == RunLifecycle.Staging)
|
||||
{
|
||||
_chargeAtLaunch = haveGoalNow ? goalSnap.Charge : 0;
|
||||
_wentInRun = false;
|
||||
}
|
||||
if (lcNow == RunLifecycle.InRoom) _wentInRun = true;
|
||||
if (lcNow == RunLifecycle.Staging && _prevRunLifecycle != RunLifecycle.Staging && _wentInRun)
|
||||
{
|
||||
if (haveGoalNow && goalSnap.Charge <= _chargeAtLaunch)
|
||||
_runFailedUntil = (float)SystemAPI.Time.ElapsedTime + 6f; // wipe/abort: nothing banked
|
||||
_wentInRun = false;
|
||||
}
|
||||
_prevRunLifecycle = lcNow;
|
||||
}
|
||||
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
|
||||
bool siege = haveCycle && cyc.Phase == CyclePhase.Siege;
|
||||
bool goalFull = SystemAPI.TryGetSingleton<GoalProgress>(out var goalNow) && goalNow.Target > 0 && goalNow.Charge >= goalNow.Target;
|
||||
@@ -224,8 +246,17 @@ namespace ProjectM.Client
|
||||
{
|
||||
case RunLifecycle.Staging:
|
||||
// The READY panel (bottom-center) owns the action + N/M count; the top line frames intent.
|
||||
_locationText.text = "AT THE BASE - build defenses, buy upgrades, READY UP to launch";
|
||||
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
|
||||
if ((float)SystemAPI.Time.ElapsedTime < _runFailedUntil)
|
||||
{
|
||||
// B6: a silent wipe used to land players home with ZERO explanation.
|
||||
_locationText.text = "EXPEDITION FAILED - the party fell; nothing was banked";
|
||||
_locationText.style.color = new Color(1f, 0.35f, 0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_locationText.text = "AT THE BASE - build defenses, buy upgrades, READY UP to launch";
|
||||
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
|
||||
}
|
||||
break;
|
||||
case RunLifecycle.Launching:
|
||||
{
|
||||
@@ -1848,6 +1879,12 @@ namespace ProjectM.Client
|
||||
Label _classTitle, _prepTitle, _portalPrompt;
|
||||
Button _classWarBtn, _classRangerBtn;
|
||||
bool _classPanelBuilt, _prepPanelBuilt, _portalBuilt;
|
||||
|
||||
// B6 run-failed read (client-local edge state; see the tracker near the top of OnUpdate).
|
||||
byte _prevRunLifecycle;
|
||||
int _chargeAtLaunch;
|
||||
bool _wentInRun;
|
||||
float _runFailedUntil;
|
||||
int _classShownFor, _prepShownFor;
|
||||
|
||||
void UpdateClassPanel(bool show, byte classId)
|
||||
|
||||
Reference in New Issue
Block a user