LANTERN purge B3+B5: delete the cycle/core/win-lose spine + onboarding; save epoch v7
Deletes CyclePhaseSystem, GoalReachedSystem, CoreDamage/CoreRestore, ThreatDirector, CoreIntegrity/GoalProgress/RunPhase/RunOutcome/ThreatState components, CoreVisualFeedbackSystem, and the whole Client/Onboarding slice (+6 test files). Keepers reworked: RunDirectorSystem (UpdateBefore attr + launch guard + goal/threat bank removed; sole SaveRequest raiser now), CycleDirectorSpawnSystem (ledger/meta host only), WaveSystem UNGATED (waves run wherever a WaveDirector is baked), EnemyAISystem core-fallback stripped, AmbientAudioSystem reworked (bed + run cues; no CycleState gate), MusicSystem RunInfo-only, HudSystem big trim (goal meter, core bar, siege banner, terminal banner, outcome flash, onboarding hook all gone), MetaShop/ClassPrep/AimReticle siege gates dropped, DebugOverlay/ops re-meant (SpawnWave=force next wave, EndSiege=quiet arena; SetCalm/AdvanceGoal/SetHeat retired, bytes reserved), TuningConfig Core knobs retired (ids 20-23 reserved), StorageMath.DrainFraction deleted, HowToPlay copy rewritten. Save epoch v7 (fresh epoch, operator-approved): SaveData drops goal/core/outcome + conveyor/machine-IO fields; MinLoadableVersion=7; PendingSave/PendingStructure trimmed; RollTerminalCampaignForward deleted; SaveStructureScan signature slimmed. 390 tests green; Play world-creation clean (player + waves live, no exceptions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,14 +26,12 @@ namespace ProjectM.Client
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
[UpdateInGroup(typeof(PresentationSystemGroup))]
|
||||
[UpdateAfter(typeof(OnboardingSystem))] // read OnboardingState.Active same-frame (single prompt voice)
|
||||
public partial class HudSystem : SystemBase
|
||||
{
|
||||
// ---- palette (Aether language; Synty white skins are tinted into these) ----
|
||||
static readonly Color AetherCyan = new(0.30f, 0.85f, 1f);
|
||||
static readonly Color OreAmber = new(1f, 0.72f, 0.35f);
|
||||
static readonly Color BioGreen = new(0.55f, 0.85f, 0.45f);
|
||||
static readonly Color CoreRed = new(1f, 0.40f, 0.32f); // END-1 Engine Core integrity bar
|
||||
|
||||
static readonly Color PanelDark = new(0.08f, 0.11f, 0.15f, 0.90f);
|
||||
static readonly Color PanelWarm = new(0.16f, 0.09f, 0.09f, 0.88f);
|
||||
@@ -43,7 +41,6 @@ namespace ProjectM.Client
|
||||
static readonly Color SlotIdleBg = new(0.09f, 0.11f, 0.15f, 0.92f);
|
||||
static readonly Color SlotSelBg = new(0.16f, 0.26f, 0.32f, 0.95f);
|
||||
static readonly Color SlotIdleBorder = new(1f, 1f, 1f, 0.08f);
|
||||
const int MaxPips = 12;
|
||||
const float ExpeditionRegionXMin = RegionMath.RegionBoundaryX; // camera x past this = the +1000 expedition region (DR-013)
|
||||
|
||||
GameObject _hudGo;
|
||||
@@ -59,18 +56,9 @@ namespace ProjectM.Client
|
||||
VisualElement _threatPanel, _threatIcon;
|
||||
Label _threatNum;
|
||||
|
||||
// macro: banner + location + goal
|
||||
VisualElement _banner, _goalContainer, _goalPipsRow, _goalBar, _goalFill;
|
||||
Label _phaseText, _cycleText, _locationText, _goalText;
|
||||
|
||||
// END-1: Engine Core integrity (losable base-heart) + overrun flash edge-detector
|
||||
VisualElement _coreContainer, _coreBar, _coreFill;
|
||||
Label _coreText;
|
||||
uint _lastOverrunTick;
|
||||
float _overrunFlashLeft;
|
||||
// END-2: terminal win/loss banner (observes the replicated RunOutcome; latched server-side).
|
||||
VisualElement _runBanner;
|
||||
Label _runBannerText, _runBannerSub;
|
||||
// macro: banner + location line
|
||||
VisualElement _banner;
|
||||
Label _phaseText, _locationText;
|
||||
// Demo polish: the clickable READY panel (Staging/Launching).
|
||||
VisualElement _readyPanel, _readyPipRow;
|
||||
Button _readyBtn;
|
||||
@@ -84,12 +72,8 @@ namespace ProjectM.Client
|
||||
VisualElement _depthPanel;
|
||||
int _depthShownFor;
|
||||
bool _depthBuilt;
|
||||
VisualElement _outcomeFlash; // one-shot gold/red full-screen flash when the outcome banner first lands
|
||||
float _outcomeFlashLeft;
|
||||
byte _outcomeFlashedFor;
|
||||
|
||||
|
||||
readonly List<VisualElement> _pips = new();
|
||||
|
||||
// resources
|
||||
Label _aetherNum, _oreNum, _bioNum;
|
||||
@@ -158,84 +142,34 @@ namespace ProjectM.Client
|
||||
bool haveTick = SystemAPI.TryGetSingleton<NetworkTime>(out var nt);
|
||||
int huskCount = _huskQuery.CalculateEntityCount();
|
||||
|
||||
// ---- 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).
|
||||
// ---- Macro banner: run-lifecycle header (the siege/cycle machinery is retired — LANTERN purge) ----
|
||||
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
|
||||
bool onRun = haveRun && runInfo.Lifecycle != RunLifecycle.Staging;
|
||||
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;
|
||||
bool finalSiege = siege && goalFull; // END-2: the climactic final siege (goal cap reached)
|
||||
bool onRun = haveRun && !siege && runInfo.Lifecycle != RunLifecycle.Staging; // mid-run: the base's Calm label is wrong
|
||||
if (haveCycle)
|
||||
{
|
||||
var endTick = new NetworkTick(cyc.PhaseEndTick);
|
||||
bool arming = haveTick && cyc.PhaseEndTick != 0 && endTick.IsValid && endTick.IsNewerThan(nt.ServerTick);
|
||||
bool finalArming = !siege && goalFull && arming; // the cap-reached arming window before the final wave
|
||||
int secs = arming ? (endTick.TicksSince(nt.ServerTick) / 60 + 1) : 0;
|
||||
string detail;
|
||||
if (siege)
|
||||
detail = (finalSiege ? "FINAL SIEGE" : "WAVE " + cyc.WaveNumber) + " - " + huskCount + " HUSKS";
|
||||
else if (arming)
|
||||
detail = (finalArming ? "FINAL SIEGE INCOMING" : "INCURSION") + " - " + secs + "s";
|
||||
else
|
||||
detail = "";
|
||||
// END-2: the climax reads distinct (intense red), not a normal incursion/wave.
|
||||
var col = finalSiege || finalArming ? new Color(1f, 0.28f, 0.22f) : PhaseColor(cyc.Phase);
|
||||
_phaseText.text = (finalSiege ? "HOLD THE ENGINE" : onRun ? "ON EXPEDITION" : PhaseLabel(cyc.Phase)) + (detail.Length > 0 ? " - " + detail : "");
|
||||
var col = onRun ? new Color(1f, 0.8f, 0.4f) : new Color(0.45f, 0.9f, 0.7f);
|
||||
_phaseText.text = onRun ? "ON EXPEDITION" : "AT BASE";
|
||||
_phaseText.style.color = col;
|
||||
_cycleText.text = "CYCLE " + cyc.CycleNumber;
|
||||
_banner.style.borderBottomColor = col;
|
||||
RetintPanel(_banner, siege ? PanelWarm : PanelDark);
|
||||
RetintPanel(_banner, PanelDark);
|
||||
}
|
||||
else
|
||||
{
|
||||
_phaseText.text = "";
|
||||
_cycleText.text = "";
|
||||
}
|
||||
|
||||
// ---- Location line (banner sub-line) — Step 14: driven by the replicated RunInfo lifecycle FSM ----
|
||||
// (the old camera-X + walk-in-gate copy died with the gate; siege/final overrides below still win).
|
||||
var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat)
|
||||
bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin;
|
||||
SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj);
|
||||
if (haveRun && !siege && !finalSiege)
|
||||
if (haveRun)
|
||||
{
|
||||
switch (runInfo.Lifecycle)
|
||||
{
|
||||
case RunLifecycle.Staging:
|
||||
// The READY panel (bottom-center) owns the action + N/M count; the top line frames intent.
|
||||
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);
|
||||
}
|
||||
_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:
|
||||
{
|
||||
@@ -280,33 +214,18 @@ namespace ProjectM.Client
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!haveRun)
|
||||
{
|
||||
_locationText.text = finalSiege
|
||||
? "FINAL SIEGE - hold the Engine, this is the last stand"
|
||||
: siege ? "DEFEND THE BASE - hold the line"
|
||||
: "MINE THE CRYSTALS - any attack harvests Ore, then BUILD";
|
||||
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f)
|
||||
: siege ? new Color(1f, 0.55f, 0.4f) : new Color(0.6f, 0.95f, 0.7f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_locationText.text = finalSiege
|
||||
? "FINAL SIEGE - hold the Engine, this is the last stand"
|
||||
: "DEFEND THE BASE - hold the line";
|
||||
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f) : new Color(1f, 0.55f, 0.4f);
|
||||
_locationText.text = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The clickable READY panel (Staging/Launching, hidden once the outcome latched — the banner owns
|
||||
// the screen then). Counts are the replicated send-to-all PlayerReady flags.
|
||||
// The clickable READY panel (Staging/Launching). Counts are the replicated send-to-all PlayerReady flags.
|
||||
int rTotal = 0, rReady = 0;
|
||||
bool localReady = false;
|
||||
int launchSecs = 0;
|
||||
bool terminal = SystemAPI.TryGetSingleton<RunOutcome>(out var readyOc)
|
||||
&& readyOc.Value != RunOutcomeId.InProgress;
|
||||
bool readyShow = haveRun && !terminal && !goalFull /* D6: goal full -> final defense armed, launching is refused server-side */
|
||||
bool readyShow = haveRun
|
||||
&& (runInfo.Lifecycle == RunLifecycle.Staging || runInfo.Lifecycle == RunLifecycle.Launching);
|
||||
if (readyShow)
|
||||
{
|
||||
@@ -352,35 +271,6 @@ namespace ProjectM.Client
|
||||
// Run-depth dots — keeps the roguelite spine visible while fighting (the map only shows at gates).
|
||||
UpdateRunDepth(haveRun ? runInfo : default, haveRun);
|
||||
|
||||
// ---- Goal (hex-pip meter, or a continuous bar for large targets) ----
|
||||
if (SystemAPI.TryGetSingleton<GoalProgress>(out var goal))
|
||||
{
|
||||
_goalContainer.style.display = DisplayStyle.Flex;
|
||||
float gfrac = goal.Target > 0 ? Mathf.Clamp01(goal.Charge / (float)goal.Target) : 0f;
|
||||
_goalText.text = "GOAL " + goal.Charge + " / " + goal.Target;
|
||||
if (goal.Target >= 1 && goal.Target <= MaxPips)
|
||||
{
|
||||
_goalPipsRow.style.display = DisplayStyle.Flex;
|
||||
_goalBar.style.display = DisplayStyle.None;
|
||||
int active = Mathf.Min(goal.Charge, goal.Target); // Charge is the integer pip count; never over-fill
|
||||
for (int i = 0; i < _pips.Count; i++)
|
||||
{
|
||||
bool show = i < goal.Target;
|
||||
_pips[i].style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (show) SetPip(_pips[i], i < active);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_goalPipsRow.style.display = DisplayStyle.None;
|
||||
_goalBar.style.display = DisplayStyle.Flex;
|
||||
HudUi.SetFill(_goalFill, gfrac);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_goalContainer.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
// ---- Resources (feed palette affordability) ----
|
||||
int aether = 0, ore = 0, bio = 0;
|
||||
@@ -400,104 +290,21 @@ namespace ProjectM.Client
|
||||
_bioNum.text = bio.ToString();
|
||||
|
||||
|
||||
// ---- Engine Core integrity (END-1): a red base-heart bar; an overrun stamps a transient pulse we flash ----
|
||||
if (SystemAPI.TryGetSingleton<CoreIntegrity>(out var core) && core.Max > 0)
|
||||
{
|
||||
_coreContainer.style.display = DisplayStyle.Flex;
|
||||
float cfrac = Mathf.Clamp01(core.Current / (float)core.Max);
|
||||
HudUi.SetFill(_coreFill, cfrac);
|
||||
_coreText.text = "CORE " + core.Current + " / " + core.Max;
|
||||
_coreText.style.color = Color.Lerp(BlightRed, CoreRed, cfrac); // shifts to danger as it drops
|
||||
if (core.OverrunTick != 0 && core.OverrunTick != _lastOverrunTick)
|
||||
{
|
||||
_lastOverrunTick = core.OverrunTick; // edge-detect the replicated breach pulse
|
||||
_overrunFlashLeft = 3.5f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_coreContainer.style.display = DisplayStyle.None;
|
||||
}
|
||||
// Overrun flash overrides the location line (runs AFTER the EB-2 cue so it wins; at a breach Phase is Calm).
|
||||
if (_overrunFlashLeft > 0f)
|
||||
{
|
||||
_overrunFlashLeft -= dt;
|
||||
_locationText.text = "BASE OVERRUN - resources lost; the Core will recover";
|
||||
_locationText.style.color = new Color(1f, 0.3f, 0.25f);
|
||||
}
|
||||
// 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.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;
|
||||
if (_outcomeFlashedFor != runOutcome.Value)
|
||||
{
|
||||
// One-shot landing flourish: full-screen color flash + camera kick — the beat gets a payoff.
|
||||
_outcomeFlashedFor = runOutcome.Value;
|
||||
_outcomeFlashLeft = win ? 0.9f : 0.7f;
|
||||
PrototypeCameraRig.PunchFov(win ? 5f : 3f, win ? 420f : 260f);
|
||||
PrototypeCameraRig.AddShake(win ? 0.25f : 0.5f);
|
||||
}
|
||||
_runBanner.style.display = DisplayStyle.Flex;
|
||||
_runBannerText.text = win ? "THE ENGINE HOLDS" : "OVERRUN";
|
||||
_runBannerText.style.color = win ? new Color(0.45f, 0.95f, 1f) : new Color(1f, 0.35f, 0.3f);
|
||||
_runBannerSub.text = win ? "VICTORY - the final siege is broken" : "THE FINAL STAND FELL";
|
||||
_runBannerSub.style.color = win ? new Color(0.7f, 0.95f, 1f) : new Color(1f, 0.6f, 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_runBanner.style.display = DisplayStyle.None;
|
||||
_outcomeFlashedFor = 0;
|
||||
}
|
||||
|
||||
// Outcome flash decay (lazy element; the banner dim sits above it, the world below).
|
||||
if (_outcomeFlashLeft > 0f)
|
||||
{
|
||||
if (_outcomeFlash == null && _doc != null && _doc.rootVisualElement != null)
|
||||
{
|
||||
_outcomeFlash = new VisualElement { pickingMode = PickingMode.Ignore };
|
||||
_outcomeFlash.style.position = Position.Absolute;
|
||||
_outcomeFlash.style.left = 0; _outcomeFlash.style.right = 0;
|
||||
_outcomeFlash.style.top = 0; _outcomeFlash.style.bottom = 0;
|
||||
_doc.rootVisualElement.Add(_outcomeFlash);
|
||||
}
|
||||
_outcomeFlashLeft -= dt;
|
||||
if (_outcomeFlash != null)
|
||||
{
|
||||
bool winFlash = _outcomeFlashedFor == RunOutcomeId.Victory;
|
||||
var fc = winFlash ? new Color(1f, 0.85f, 0.35f) : new Color(1f, 0.20f, 0.15f);
|
||||
_outcomeFlash.style.backgroundColor = new Color(fc.r, fc.g, fc.b, Mathf.Clamp01(_outcomeFlashLeft) * 0.35f);
|
||||
_outcomeFlash.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
else if (_outcomeFlash != null)
|
||||
{
|
||||
_outcomeFlash.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- Threat readout (top-right) — hidden entirely at base with zero husks; its reappearance is the cue ----
|
||||
bool showThreat = siege || huskCount > 0;
|
||||
// ---- Threat readout (top-right) — hidden entirely with zero husks; its reappearance is the cue ----
|
||||
bool showThreat = huskCount > 0;
|
||||
_threatPanel.style.display = showThreat ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (showThreat)
|
||||
{
|
||||
float intensity = Mathf.Clamp01(huskCount / 30f);
|
||||
Color tc = siege ? Color.Lerp(ThreatWarm, BlightRed, intensity) : ThreatWarm;
|
||||
Color tc = Color.Lerp(ThreatWarm, BlightRed, intensity);
|
||||
_threatNum.text = huskCount.ToString();
|
||||
_threatNum.style.color = tc;
|
||||
_threatIcon.style.unityBackgroundImageTintColor = tc;
|
||||
RetintPanel(_threatPanel, siege ? PanelWarm : PanelDark);
|
||||
RetintPanel(_threatPanel, PanelDark);
|
||||
}
|
||||
|
||||
// ---- Build palette + control hints (bottom-center) ----
|
||||
@@ -546,7 +353,7 @@ namespace ProjectM.Client
|
||||
break;
|
||||
}
|
||||
|
||||
_doc.rootVisualElement.style.display = (found || haveCycle) ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
_doc.rootVisualElement.style.display = (found || haveRun) ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
// ---- Low-health vignette + hurt flash (full-screen) ----
|
||||
_flash = HudVisualMath.DecayFlash(_flash, dt);
|
||||
@@ -645,22 +452,7 @@ namespace ProjectM.Client
|
||||
else p.style.backgroundColor = c;
|
||||
}
|
||||
|
||||
void SetPip(VisualElement pip, bool active)
|
||||
{
|
||||
var theme = HudTheme.Get();
|
||||
var spr = active ? theme?.PipActive : theme?.PipInactive;
|
||||
if (spr != null)
|
||||
{
|
||||
pip.style.backgroundImage = new StyleBackground(Background.FromSprite(spr));
|
||||
pip.style.unityBackgroundImageTintColor = active ? AetherCyan : PipDim;
|
||||
pip.style.backgroundSize = new StyleBackgroundSize(new BackgroundSize(BackgroundSizeType.Contain));
|
||||
}
|
||||
else
|
||||
{
|
||||
pip.style.backgroundColor = active ? AetherCyan : PipDim;
|
||||
MenuUi.Round(pip, 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// LANTERN purge: the automation buildables are deleted; Pylon stays hidden from the build palette (cosmetic-only).
|
||||
static bool IsPaletteType(byte type) => type != StructureType.Pylon;
|
||||
@@ -804,7 +596,6 @@ namespace ProjectM.Client
|
||||
BuildDiscoveryChip(root);
|
||||
BuildDowned(root);
|
||||
BuildInventory(root);
|
||||
BuildRunBanner(root);
|
||||
}
|
||||
|
||||
void BuildVignette(VisualElement root)
|
||||
@@ -932,69 +723,13 @@ namespace ProjectM.Client
|
||||
_banner.Add(bIcon);
|
||||
_phaseText = HudUi.Display("", 30, AetherCyan, TextAnchor.MiddleCenter);
|
||||
_banner.Add(_phaseText);
|
||||
_cycleText = HudUi.Text("", 14, MenuUi.SubCol, TextAnchor.MiddleCenter);
|
||||
_cycleText.style.marginLeft = 14;
|
||||
_banner.Add(_cycleText);
|
||||
macro.Add(_banner);
|
||||
|
||||
_locationText = HudUi.Text("", 15, new Color(0.6f, 0.85f, 1f), TextAnchor.MiddleCenter);
|
||||
_locationText.style.marginTop = 5;
|
||||
macro.Add(_locationText);
|
||||
|
||||
// goal: hex-pip meter (or fallback bar) + numeral
|
||||
_goalContainer = HudUi.Group(Align.Center);
|
||||
_goalContainer.style.marginTop = 8;
|
||||
|
||||
var goalLine = new VisualElement();
|
||||
goalLine.style.flexDirection = FlexDirection.Row;
|
||||
goalLine.style.alignItems = Align.Center;
|
||||
goalLine.pickingMode = PickingMode.Ignore;
|
||||
|
||||
_goalPipsRow = new VisualElement();
|
||||
_goalPipsRow.style.flexDirection = FlexDirection.Row;
|
||||
_goalPipsRow.style.alignItems = Align.Center;
|
||||
_goalPipsRow.pickingMode = PickingMode.Ignore;
|
||||
for (int i = 0; i < MaxPips; i++)
|
||||
{
|
||||
var pip = new VisualElement();
|
||||
pip.style.width = 22; pip.style.height = 22;
|
||||
pip.style.marginLeft = 2; pip.style.marginRight = 2;
|
||||
pip.style.flexShrink = 0;
|
||||
pip.pickingMode = PickingMode.Ignore;
|
||||
pip.style.display = DisplayStyle.None;
|
||||
_pips.Add(pip);
|
||||
_goalPipsRow.Add(pip);
|
||||
}
|
||||
goalLine.Add(_goalPipsRow);
|
||||
|
||||
_goalText = HudUi.Display("GOAL 0 / 10", 16, AetherCyan, TextAnchor.MiddleCenter);
|
||||
_goalText.style.marginLeft = 10;
|
||||
goalLine.Add(_goalText);
|
||||
_goalContainer.Add(goalLine);
|
||||
|
||||
// fallback continuous bar (large targets)
|
||||
_goalBar = HudUi.Bar(360, 16, new Color(0.8f, 0.6f, 1f), out _goalFill);
|
||||
_goalBar.style.marginTop = 4;
|
||||
_goalBar.style.display = DisplayStyle.None;
|
||||
_goalContainer.Add(_goalBar);
|
||||
|
||||
macro.Add(_goalContainer);
|
||||
|
||||
// END-1: Engine Core integrity bar (red) — the losable base-heart meter.
|
||||
_coreContainer = HudUi.Group(Align.Center);
|
||||
_coreContainer.style.marginTop = 6;
|
||||
var coreLine = new VisualElement();
|
||||
coreLine.style.flexDirection = FlexDirection.Row;
|
||||
coreLine.style.alignItems = Align.Center;
|
||||
coreLine.pickingMode = PickingMode.Ignore;
|
||||
_coreBar = HudUi.Bar(360, 14, CoreRed, out _coreFill);
|
||||
coreLine.Add(_coreBar);
|
||||
_coreText = HudUi.Text("CORE 100 / 100", 13, CoreRed, TextAnchor.MiddleLeft);
|
||||
_coreText.style.marginLeft = 10;
|
||||
coreLine.Add(_coreText);
|
||||
_coreContainer.Add(coreLine);
|
||||
_coreContainer.style.display = DisplayStyle.None;
|
||||
macro.Add(_coreContainer);
|
||||
|
||||
root.Add(macro);
|
||||
}
|
||||
@@ -1108,53 +843,7 @@ namespace ProjectM.Client
|
||||
_downed.style.display = DisplayStyle.None;
|
||||
root.Add(_downed);
|
||||
}
|
||||
void BuildRunBanner(VisualElement root)
|
||||
{
|
||||
_runBanner = new VisualElement();
|
||||
_runBanner.style.position = Position.Absolute;
|
||||
_runBanner.style.left = 0; _runBanner.style.right = 0; _runBanner.style.top = 0; _runBanner.style.bottom = 0;
|
||||
_runBanner.style.alignItems = Align.Center;
|
||||
_runBanner.style.justifyContent = Justify.Center;
|
||||
_runBanner.pickingMode = PickingMode.Ignore;
|
||||
_runBanner.style.backgroundColor = new Color(0.02f, 0.03f, 0.05f, 0.55f);
|
||||
var col = HudUi.Group(Align.Center);
|
||||
_runBannerText = HudUi.Display("", 72, Color.white, TextAnchor.MiddleCenter);
|
||||
col.Add(_runBannerText);
|
||||
_runBannerSub = HudUi.Text("", 22, MenuUi.SubCol, TextAnchor.MiddleCenter);
|
||||
_runBannerSub.style.marginTop = 8;
|
||||
col.Add(_runBannerSub);
|
||||
// END-2 (SL-5): the terminal banner offers a clear action so the player isn't hunting for Esc.
|
||||
// SINGLE: PLAY AGAIN Continues as a fresh campaign (base+meta kept — the terminal save rolls
|
||||
// forward on stage). CO-OP (operator-locked): the honest exit is a clean teardown for everyone —
|
||||
// the host ends the session (each joiner's ConnectionWatchdog returns them to the menu with a
|
||||
// reason), a joiner just leaves. All self-guard on WorldLauncher.Busy. The row picks (Position)
|
||||
// even though the banner root Ignores.
|
||||
var btnRow = new VisualElement();
|
||||
btnRow.style.flexDirection = FlexDirection.Row;
|
||||
btnRow.style.justifyContent = Justify.Center;
|
||||
btnRow.style.marginTop = 28;
|
||||
btnRow.pickingMode = PickingMode.Position;
|
||||
switch (WorldLauncher.LastMode)
|
||||
{
|
||||
case SessionMode.Host:
|
||||
btnRow.Add(MenuUi.Button("END SESSION — ALL TO MENU", WorldLauncher.TeardownToMenu));
|
||||
break;
|
||||
case SessionMode.Join:
|
||||
btnRow.Add(MenuUi.Button("LEAVE TO MENU", WorldLauncher.TeardownToMenu));
|
||||
break;
|
||||
default:
|
||||
var again = MenuUi.Button("PLAY AGAIN",
|
||||
() => WorldLauncher.StartSession(SessionMode.Single, null, SaveService.HasSave()));
|
||||
again.style.marginRight = 12;
|
||||
btnRow.Add(again);
|
||||
btnRow.Add(MenuUi.Button("QUIT TO MENU", WorldLauncher.TeardownToMenu));
|
||||
break;
|
||||
}
|
||||
col.Add(btnRow);
|
||||
_runBanner.Add(col);
|
||||
_runBanner.style.display = DisplayStyle.None;
|
||||
root.Add(_runBanner);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void BuildInventory(VisualElement root)
|
||||
@@ -1280,25 +969,9 @@ namespace ProjectM.Client
|
||||
}
|
||||
|
||||
|
||||
static Color PhaseColor(byte phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case CyclePhase.Calm: return new Color(0.45f, 0.9f, 0.7f);
|
||||
case CyclePhase.Siege: return new Color(1f, 0.45f, 0.3f);
|
||||
default: return Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
static string PhaseLabel(byte phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case CyclePhase.Calm: return "AT BASE";
|
||||
case CyclePhase.Siege: return "UNDER SIEGE";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string StructureName(byte type)
|
||||
{
|
||||
@@ -1509,11 +1182,6 @@ namespace ProjectM.Client
|
||||
}
|
||||
|
||||
|
||||
// B6 run-failed read (client-local edge state; see the tracker near the top of OnUpdate).
|
||||
byte _prevRunLifecycle;
|
||||
int _chargeAtLaunch;
|
||||
bool _wentInRun;
|
||||
float _runFailedUntil;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user