102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import Phaser from 'phaser';
|
|
import { POUR_SWEEP_MS, POUR_CLEAN_FROM, POUR_OVER_FROM } from '../barShift';
|
|
import { POUR_UI } from '../../../data/strings/floor';
|
|
|
|
const W = 640;
|
|
const H = 360;
|
|
const GLASS_H = 120;
|
|
const GLASS_W = 44;
|
|
|
|
/**
|
|
* The pour (docs/SCENARIOS.md, bartender shift): a marker sweeps 0→1→0 and
|
|
* SPACE stops it — the fill IS your pour. The clean band sits near the top
|
|
* because a good pour is nearly a mistake. Same shell discipline as the other
|
|
* overlays: the scene routes keys in and calls update(dt).
|
|
*/
|
|
export class PourOverlay {
|
|
private root: Phaser.GameObjects.Container | null = null;
|
|
private fillRect: Phaser.GameObjects.Rectangle | null = null;
|
|
private t = 0;
|
|
private rising = true;
|
|
private done: ((fill: number | null) => void) | null = null;
|
|
|
|
constructor(private readonly scene: Phaser.Scene) {}
|
|
|
|
get isOpen(): boolean {
|
|
return this.root !== null;
|
|
}
|
|
|
|
open(done: (fill: number | null) => void): void {
|
|
if (this.root) return;
|
|
this.done = done;
|
|
this.t = 0;
|
|
this.rising = true;
|
|
|
|
const root = this.scene.add.container(0, 0).setDepth(900).setScrollFactor(0);
|
|
this.root = root;
|
|
root.add(this.scene.add.rectangle(W / 2, H / 2, W, H, 0x03030a, 0.72));
|
|
root.add(this.scene.add.rectangle(W / 2, H / 2, 280, 200, 0x10121c).setStrokeStyle(1, 0x3a4054));
|
|
root.add(
|
|
this.scene.add
|
|
.text(W / 2, H / 2 - 82, POUR_UI.title, { fontFamily: 'monospace', fontSize: '13px', color: '#e8e4da' })
|
|
.setOrigin(0.5),
|
|
);
|
|
|
|
// The glass, and the bands painted on its side like an honest jug.
|
|
const gx = W / 2;
|
|
const gBottom = H / 2 + 58;
|
|
root.add(this.scene.add.rectangle(gx, gBottom - GLASS_H / 2, GLASS_W + 8, GLASS_H + 6, 0x232838));
|
|
const band = (from: number, to: number, colour: number): void => {
|
|
const y0 = gBottom - GLASS_H * from;
|
|
const y1 = gBottom - GLASS_H * to;
|
|
root.add(
|
|
this.scene.add
|
|
.rectangle(gx + GLASS_W / 2 + 10, (y0 + y1) / 2, 4, y0 - y1, colour)
|
|
.setOrigin(0.5),
|
|
);
|
|
};
|
|
band(0, POUR_CLEAN_FROM, 0x5a4a48); // short: the region of regret
|
|
band(POUR_CLEAN_FROM, POUR_OVER_FROM, 0x74a06a); // clean
|
|
band(POUR_OVER_FROM, 1, 0xd8a020); // overflow: beloved, expensive
|
|
this.fillRect = this.scene.add
|
|
.rectangle(gx, gBottom, GLASS_W, 1, 0xc8a24a)
|
|
.setOrigin(0.5, 1);
|
|
root.add(this.fillRect);
|
|
|
|
root.add(
|
|
this.scene.add
|
|
.text(W / 2, H / 2 + 78, POUR_UI.help, { fontFamily: 'monospace', fontSize: '9px', color: '#8890a8' })
|
|
.setOrigin(0.5),
|
|
);
|
|
}
|
|
|
|
handleKey(key: string): void {
|
|
if (!this.root) return;
|
|
if (key === 'ESCAPE' || key === 'ESC') return this.finish(null);
|
|
if (key === 'SPACE') return this.finish(this.t);
|
|
}
|
|
|
|
update(dtMs: number): void {
|
|
if (!this.root) return;
|
|
const step = dtMs / POUR_SWEEP_MS;
|
|
this.t += this.rising ? step : -step;
|
|
if (this.t >= 1) {
|
|
this.t = 1;
|
|
this.rising = false;
|
|
} else if (this.t <= 0) {
|
|
this.t = 0;
|
|
this.rising = true;
|
|
}
|
|
this.fillRect?.setDisplaySize(GLASS_W, Math.max(1, GLASS_H * this.t));
|
|
}
|
|
|
|
private finish(fill: number | null): void {
|
|
const cb = this.done;
|
|
this.root?.destroy();
|
|
this.root = null;
|
|
this.fillRect = null;
|
|
this.done = null;
|
|
cb?.(fill);
|
|
}
|
|
}
|