[ui] wire round 5: tuner reveal, screen enlarge, notices, camera hint
The HUD root gains the tuner and the enlarge overlay, the T/arrows/B/M keys, and the small amount of session state the onboarding needs: the mercy toast fires once ever, hatch warnings are remembered per pile. The fossil reveals the dock from either direction — the relicFound event, or a loaded save where relics[].found is already true and no event is coming. The dock lands in the toast stack's corner, so the toasts stand off above it rather than printing over the dial. Esc now closes the most-recently-fronted thing first: enlarge, then the held tool, then panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d6d6833b78
commit
0bf0e5a076
@ -13,11 +13,14 @@ import { createFax } from './fax';
|
||||
import { createHotkeys } from './hotkeys';
|
||||
import { createInspector } from './inspector';
|
||||
import { createRoster, entityAt, syncRoster } from './pick';
|
||||
import { createScreenView } from './screenview';
|
||||
import { createSelection } from './selection';
|
||||
import { injectStyle } from './style';
|
||||
import { createTechPanel } from './techpanel';
|
||||
import { createTopStrip } from './topstrip';
|
||||
import { createToasts } from './toasts';
|
||||
import { createTuner } from './tuner';
|
||||
import { COPY } from './voice';
|
||||
|
||||
export function createUI(): UI {
|
||||
let machines = new Map<string, MachineDef>();
|
||||
@ -27,11 +30,25 @@ export function createUI(): UI {
|
||||
let fax: ReturnType<typeof createFax>;
|
||||
let toasts: ReturnType<typeof createToasts>;
|
||||
let tech: ReturnType<typeof createTechPanel>;
|
||||
let tuner: ReturnType<typeof createTuner>;
|
||||
let screenView: ReturnType<typeof createScreenView>;
|
||||
|
||||
/** Our own copy of the entity list; safe to read between frames. See pick.ts. */
|
||||
const roster = createRoster();
|
||||
let paused = false;
|
||||
|
||||
// ---- onboarding + ceremony state (per session; none of it is sim truth) ----
|
||||
/** The opening's one hint. Fires on the first brownout ever, then never again. */
|
||||
let mercyShown = false;
|
||||
/** Dust piles we've already warned about, so a pile isn't a recurring nag. */
|
||||
const warnedPiles = new Set<number>();
|
||||
|
||||
function revealTuner() {
|
||||
tuner.reveal();
|
||||
// The dock lands in the toasts' corner; move them up so it can't print over the dial.
|
||||
toasts.setStandoff(true);
|
||||
}
|
||||
|
||||
function machineName(entityId: number): string {
|
||||
for (let i = 0; i < roster.length; i++) {
|
||||
const r = roster.entries[i];
|
||||
@ -48,12 +65,25 @@ export function createUI(): UI {
|
||||
const sel = createSelection(bus);
|
||||
top = createTopStrip(data);
|
||||
build = createBuildBar(data, sel);
|
||||
inspector = createInspector(data, bus);
|
||||
// v5: closing the inspector — by X, by Esc, or because its unit was demolished —
|
||||
// must stop the renderer highlighting it.
|
||||
inspector = createInspector(data, bus, () => sel.clearInspect());
|
||||
fax = createFax(data);
|
||||
toasts = createToasts();
|
||||
tech = createTechPanel(data, bus);
|
||||
tuner = createTuner();
|
||||
screenView = createScreenView(document.getElementById('screen'));
|
||||
|
||||
root.append(top.el, build.el, inspector.el, fax.el, tech.el, toasts.el);
|
||||
// A held tool and an open inspector are mutually exclusive: picking a machine or
|
||||
// arming remove supersedes the inspect selection, so shut the panel to match.
|
||||
sel.onChange(() => {
|
||||
if (sel.isArmed() && inspector.isOpen()) inspector.close();
|
||||
});
|
||||
|
||||
root.append(
|
||||
top.el, build.el, inspector.el, fax.el, tech.el,
|
||||
tuner.el, screenView.el, toasts.el,
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------ keyboard
|
||||
const keys = createHotkeys();
|
||||
@ -63,10 +93,14 @@ export function createUI(): UI {
|
||||
keys.bind('r', () => sel.rotate());
|
||||
keys.bind('x', () => sel.toggleRemove());
|
||||
keys.bind('t', () => tech.toggle());
|
||||
// Tuner keys are inert until the dock is revealed, so they don't shadow anything
|
||||
// during the pre-discovery game.
|
||||
for (const k of ['arrowleft', 'arrowright', 'b', 'm']) keys.bind(k, () => tuner.onKey(k));
|
||||
keys.bind('escape', () => {
|
||||
// One key, several jobs, in the order the player expects: put the tool down
|
||||
// first (wrecking ball included), then shut whatever is open.
|
||||
if (sel.isArmed()) sel.clear();
|
||||
// One key, several jobs, in the order the player expects: close the thing most
|
||||
// recently in front first, then put the tool down, then shut panels.
|
||||
if (screenView.isOpen()) screenView.close();
|
||||
else if (sel.isArmed()) sel.clear();
|
||||
else if (tech.isOpen()) tech.close();
|
||||
else inspector.close();
|
||||
});
|
||||
@ -97,8 +131,12 @@ export function createUI(): UI {
|
||||
if (!tile) return;
|
||||
|
||||
const hit = entityAt(roster, machines, tile);
|
||||
if (hit === null) inspector.close();
|
||||
else inspector.open(hit);
|
||||
if (hit === null) {
|
||||
inspector.close();
|
||||
} else {
|
||||
inspector.open(hit);
|
||||
sel.inspect(hit); // v5: renderer highlights the unit we're showing
|
||||
}
|
||||
});
|
||||
|
||||
// ?uidemo — build SIM's reference factory on boot. main.ts defines __fktryDemo
|
||||
@ -122,9 +160,30 @@ export function createUI(): UI {
|
||||
fax.update(snap);
|
||||
tech.update(snap);
|
||||
|
||||
// The fossil may already be dug up in a loaded save, with no event to announce it.
|
||||
if (!tuner.isRevealed() && snap.relics?.some((r) => r.found)) revealTuner();
|
||||
|
||||
for (const ev of events) {
|
||||
toasts.push(ev, machineName);
|
||||
if (ev.kind === 'commissionDone') fax.stamp();
|
||||
|
||||
if (ev.kind === 'relicFound') revealTuner(); // ceremony toast comes from voice.ts
|
||||
|
||||
// One hatch warning per pile. `spawned` is edge-triggered so this is usually
|
||||
// already true; the guard keeps a re-emitting sim from turning it into a nag.
|
||||
if (ev.kind === 'wildlife' && ev.wildlife === 'dust-pile') {
|
||||
if (ev.on === 'cleared') warnedPiles.delete(ev.id);
|
||||
else if (warnedPiles.has(ev.id)) continue;
|
||||
else warnedPiles.add(ev.id);
|
||||
}
|
||||
|
||||
toasts.push(ev, machineName);
|
||||
|
||||
// The alarm alone teaches nothing. Once per session, on the very first brownout,
|
||||
// say what to do about it.
|
||||
if (ev.kind === 'brownout' && ev.on && !mercyShown) {
|
||||
mercyShown = true;
|
||||
toasts.pushMessage(COPY.firstBrownoutMercy, 'mercy');
|
||||
}
|
||||
}
|
||||
toasts.update(performance.now());
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user