Combat feel: correct boss bar, player hit-flash, dash-to-move, finisher hit-stop
Boss bar reconstructs the true max client-side (Health.Max unreplicated) via Kind==Charger + expedition-pos filter; boss slam telegraph = a full ground ring at BossSlamRadius; IsAttacking OR's IsLunging. Player body flash on hurt (distinct red, jitter-safe threshold). Dash toward movement input when moving else facing. Warrior cone: muzzle-cue suppressed (cone arc is its cue). Minimal finisher-only hit-stop (PrototypeCameraRig.Hold, behind FeelConfig flag; never Time.timeScale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ using System.Collections.Generic;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms; // A6: boss-bar query reads LocalTransform (source-gen needs the using in this file)
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
@@ -317,7 +319,7 @@ namespace ProjectM.Client
|
||||
int launchSecs = 0;
|
||||
bool terminal = SystemAPI.TryGetSingleton<RunOutcome>(out var readyOc)
|
||||
&& readyOc.Value != RunOutcomeId.InProgress;
|
||||
bool readyShow = haveRun && !terminal
|
||||
bool readyShow = haveRun && !terminal && !goalFull /* D6: goal full -> final defense armed, launching is refused server-side */
|
||||
&& (runInfo.Lifecycle == RunLifecycle.Staging || runInfo.Lifecycle == RunLifecycle.Launching);
|
||||
if (readyShow)
|
||||
{
|
||||
@@ -337,21 +339,28 @@ namespace ProjectM.Client
|
||||
}
|
||||
UpdateReadyPanel(readyShow, runInfo, rTotal, rReady, localReady, launchSecs);
|
||||
|
||||
// Boss presence bar — client heuristic: a Boss room spawns exactly ONE enemy (the boss), so any live
|
||||
// EnemyTag Health in a Boss-type room is it. Replicated state only; no netcode surface.
|
||||
// Boss presence bar. The boss is a scaled Charger (EnemyTelegraph.Kind==KindCharger, baked/client-safe)
|
||||
// in the EXPEDITION region — filtering on both excludes phase-two summoned swarmers AND a base-region
|
||||
// siege enemy a dead teammate can see. Health.Max is NOT replicated, so reconstruct the true max from the
|
||||
// baked Charger Max × the shared BossHealthMultiplier (A6 client fix; zero ghost-hash change).
|
||||
bool bossAlive = false;
|
||||
float bossHp = 0f, bossMax = 0f;
|
||||
if (haveRun && runInfo.Lifecycle == RunLifecycle.InRoom && runInfo.CurrentRoomType == RoomTypeId.Boss)
|
||||
{
|
||||
foreach (var bhq in SystemAPI.Query<RefRO<Health>>().WithAll<EnemyTag>())
|
||||
float bossBakedMax = 0f;
|
||||
foreach (var (bhq, tele, blt) in
|
||||
SystemAPI.Query<RefRO<Health>, RefRO<EnemyTelegraph>, RefRO<LocalTransform>>().WithAll<EnemyTag>())
|
||||
{
|
||||
if (bhq.ValueRO.Max > bossMax)
|
||||
if (tele.ValueRO.Kind != ZoneEnemyMath.KindCharger) continue; // the boss is a Charger; skip summoned swarmers
|
||||
if (blt.ValueRO.Position.x <= ExpeditionRegionXMin) continue; // expedition only (not a base siege enemy)
|
||||
if (bhq.ValueRO.Max > bossBakedMax)
|
||||
{
|
||||
bossMax = bhq.ValueRO.Max;
|
||||
bossBakedMax = bhq.ValueRO.Max;
|
||||
bossHp = bhq.ValueRO.Current;
|
||||
bossAlive = bhq.ValueRO.Current > 0f;
|
||||
}
|
||||
}
|
||||
bossMax = bossBakedMax * Tuning.BossHealthMultiplier;
|
||||
}
|
||||
UpdateBossBar(bossAlive, bossHp, bossMax);
|
||||
|
||||
@@ -435,7 +444,7 @@ namespace ProjectM.Client
|
||||
// broken turret): a dry base during a siege tells the player to build a Fabricator.
|
||||
if (siege && charge == 0 && !onExpedition)
|
||||
{
|
||||
_locationText.text = "TURRETS OUT OF CHARGE - build a Fabricator (Ore -> Charge)";
|
||||
_locationText.text = "TURRETS OUT OF AMMO - build a Fabricator (Ore -> ammo)";
|
||||
_locationText.style.color = new Color(1f, 0.4f, 0.9f);
|
||||
}
|
||||
|
||||
@@ -466,8 +475,16 @@ namespace ProjectM.Client
|
||||
}
|
||||
// First-run onboarding owns the prompt voice: while a coach-mark step is showing, blank the HUD's own
|
||||
// location/gate hint so the player sees a single prompt (OnboardingSystem drives its own overlay).
|
||||
if (OnboardingState.Active) _locationText.text = "";
|
||||
// ---- END-2: terminal run banner (Victory / Loss), observed from the replicated RunOutcome ----
|
||||
if (OnboardingState.SuppressLocationLine) _locationText.text = ""; // D4: blank only for the early base-framing steps; room/siege/charge cues survive
|
||||
// D6: goal full but the final siege hasn't spawned yet (the arming gap) -> the READY panel is hidden; tell the
|
||||
// player what's coming instead of a stale base line (goalFull is replicated; RunPhase is server-only).
|
||||
if (haveRun && goalFull && !terminal && !siege && !finalSiege)
|
||||
{
|
||||
_locationText.text = "GOAL REACHED - FINAL DEFENSE INCOMING: hold the Engine!";
|
||||
_locationText.style.color = new Color(1f, 0.35f, 0.28f);
|
||||
}
|
||||
|
||||
// ---- END-2: terminal run banner (Victory / Loss), observed from the replicated RunOutcome ----
|
||||
if (SystemAPI.TryGetSingleton<RunOutcome>(out var runOutcome) && runOutcome.Value != RunOutcomeId.InProgress)
|
||||
{
|
||||
bool win = runOutcome.Value == RunOutcomeId.Victory;
|
||||
|
||||
Reference in New Issue
Block a user