@energy8platform/shell 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html.cjs.js +14 -18
- package/dist/html.cjs.js.map +1 -1
- package/dist/html.d.ts +16 -8
- package/dist/html.esm.js +14 -18
- package/dist/html.esm.js.map +1 -1
- package/dist/index.cjs.js +7 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.esm.js +7 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/pixi.cjs.js +21 -18
- package/dist/pixi.cjs.js.map +1 -1
- package/dist/pixi.d.ts +16 -10
- package/dist/pixi.esm.js +21 -18
- package/dist/pixi.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ShellController.ts +7 -5
- package/src/core/renderer.ts +9 -2
- package/src/core/version.ts +1 -1
- package/src/ui/html/HtmlRenderer.ts +7 -13
- package/src/ui/pixi/PixiRenderer.ts +11 -18
- package/src/ui/pixi/text.ts +5 -0
package/package.json
CHANGED
|
@@ -304,9 +304,9 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
// ── game-facing public API (mirrors GameShell/PixiGameShell) ───────────────────
|
|
307
|
-
private money(field: 'balance' | 'win', from: number, to: number): void {
|
|
307
|
+
private money(field: 'balance' | 'win', from: number, to: number, durationMs?: number): void {
|
|
308
308
|
this.renderer.renderBar();
|
|
309
|
-
if (to !== from) this.renderer.animateMoney(field, from, to);
|
|
309
|
+
if (to !== from) this.renderer.animateMoney(field, from, to, durationMs);
|
|
310
310
|
}
|
|
311
311
|
setBalance(n: number): void {
|
|
312
312
|
const from = this.prevBalance;
|
|
@@ -316,8 +316,10 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
316
316
|
}
|
|
317
317
|
/** Set the WIN readout. Counts up/down from the previous value by default. Pass
|
|
318
318
|
* `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
|
|
319
|
-
* host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong.
|
|
320
|
-
|
|
319
|
+
* host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong.
|
|
320
|
+
* `{ durationMs }` shortens/lengthens the count-up (default 450ms) — a scene reporting a win per
|
|
321
|
+
* cascade step passes its step length so each count-up finishes before the next step lands. */
|
|
322
|
+
setWin(n: number, opts?: { animate?: boolean; durationMs?: number }): void {
|
|
321
323
|
const from = this.prevWin;
|
|
322
324
|
this.state.win = n;
|
|
323
325
|
this.prevWin = n;
|
|
@@ -325,7 +327,7 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
325
327
|
this.renderer.renderBar(); // instant repaint from state; cancels running money anims
|
|
326
328
|
return;
|
|
327
329
|
}
|
|
328
|
-
this.money('win', from, n);
|
|
330
|
+
this.money('win', from, n, opts?.durationMs);
|
|
329
331
|
}
|
|
330
332
|
setBet(n: number): void {
|
|
331
333
|
this.state.bet = n;
|
package/src/core/renderer.ts
CHANGED
|
@@ -18,8 +18,10 @@ export interface ShellRenderer {
|
|
|
18
18
|
setLayout(layout: ShellLayoutMode): void;
|
|
19
19
|
/** Apply colour tokens (CSS vars in DOM / repaint in Pixi). */
|
|
20
20
|
applyTheme(tokens: ShellTokens): void;
|
|
21
|
-
/** Count a money readout from→to on the freshly-rendered bar (DOM rAF / Pixi ticker).
|
|
22
|
-
|
|
21
|
+
/** Count a money readout from→to on the freshly-rendered bar (DOM rAF / Pixi ticker).
|
|
22
|
+
* `durationMs` overrides the renderer's default count-up length — a cascade/tumble scene
|
|
23
|
+
* reporting a win per step needs each count-up to fit inside its step. */
|
|
24
|
+
animateMoney(field: 'balance' | 'win', from: number, to: number, durationMs?: number): void;
|
|
23
25
|
/** Build + show an overlay from a controller-supplied model; return a handle for key routing
|
|
24
26
|
* and programmatic close. Returns void when nothing was shown. */
|
|
25
27
|
openOverlay(req: OverlayRequest): OverlayHandle | void;
|
|
@@ -86,6 +88,11 @@ export interface ShellHost {
|
|
|
86
88
|
setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
|
|
87
89
|
/** Logic-bearing actions invoked by renderer controls. */
|
|
88
90
|
readonly actions: ShellActions;
|
|
91
|
+
/** Re-show the replay summary modal through the controller (keeps its OverlayHandle in sync).
|
|
92
|
+
* Used as the modal's own reopen callback after START REPLAY, so a renderer never re-pushes a
|
|
93
|
+
* replay modal behind the controller's back (which would strand `overlay` and dead-end the
|
|
94
|
+
* next close). */
|
|
95
|
+
openReplay(opts: ReplayModalOptions): void;
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
/** Every state-changing thing a control can do. Each runs logic in the controller, emits the
|
package/src/core/version.ts
CHANGED
|
@@ -19,10 +19,10 @@ const REMOVE_FADE_MS = 300;
|
|
|
19
19
|
|
|
20
20
|
/** Count-up the value span (.ge-rd-val) of a readout, leaving its label untouched.
|
|
21
21
|
* Returns the count-up canceler so the renderer can stop it before the node is replaced. */
|
|
22
|
-
function animateReadout(el: HTMLElement, from: number, to: number, fmt: (n: number) => string): () => void {
|
|
22
|
+
function animateReadout(el: HTMLElement, from: number, to: number, fmt: (n: number) => string, durationMs?: number): () => void {
|
|
23
23
|
const val = el.querySelector('.ge-rd-val') as HTMLElement | null;
|
|
24
24
|
if (!val) { el.textContent = fmt(to); return () => {}; }
|
|
25
|
-
return countUp(val, from, to, fmt);
|
|
25
|
+
return durationMs == null ? countUp(val, from, to, fmt) : countUp(val, from, to, fmt, durationMs);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export class HtmlRenderer implements ShellRenderer {
|
|
@@ -87,10 +87,10 @@ export class HtmlRenderer implements ShellRenderer {
|
|
|
87
87
|
this.renderBar();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
animateMoney(field: 'balance' | 'win', from: number, to: number): void {
|
|
90
|
+
animateMoney(field: 'balance' | 'win', from: number, to: number, durationMs?: number): void {
|
|
91
91
|
const fmt = (n: number) => this.host.formatCurrency(n, field === 'win');
|
|
92
92
|
const el = this.barHost.querySelector(`[data-ge="${field}"]`) as HTMLElement | null;
|
|
93
|
-
if (el) this.moneyAnims.push(animateReadout(el, from, to, fmt));
|
|
93
|
+
if (el) this.moneyAnims.push(animateReadout(el, from, to, fmt, durationMs));
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
openOverlay(req: OverlayRequest): OverlayHandle | void {
|
|
@@ -162,7 +162,9 @@ export class HtmlRenderer implements ShellRenderer {
|
|
|
162
162
|
}
|
|
163
163
|
case 'replay': {
|
|
164
164
|
const opts = req.opts;
|
|
165
|
-
|
|
165
|
+
// Reopen through the controller (not a renderer-direct re-push) so its OverlayHandle stays
|
|
166
|
+
// in sync — otherwise a reopened replay modal is untracked and the next close no-ops.
|
|
167
|
+
const reopen = (): void => { this.host.openReplay(opts); };
|
|
166
168
|
const root = buildReplayModal(this.host, opts, reopen);
|
|
167
169
|
return { root };
|
|
168
170
|
}
|
|
@@ -173,14 +175,6 @@ export class HtmlRenderer implements ShellRenderer {
|
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
/** Opens a replay modal and shows it — used as the reopen callback after START REPLAY. */
|
|
177
|
-
private openReplayInternal(opts: import('@/core/types').ReplayModalOptions): void {
|
|
178
|
-
if (this.destroyed) return;
|
|
179
|
-
const reopen = (): void => { this.openReplayInternal(opts); };
|
|
180
|
-
const root = buildReplayModal(this.host, opts, reopen);
|
|
181
|
-
this.showModal(root);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
178
|
private showModal(el: HTMLElement, onKey?: (e: KeyboardEvent) => boolean): void {
|
|
185
179
|
// Drop focus from any open shell control so a stray Space/Enter doesn't re-activate it
|
|
186
180
|
const active = document.activeElement as HTMLElement | null;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
RenderTexture,
|
|
7
7
|
Sprite,
|
|
8
8
|
type Application,
|
|
9
|
+
type Text,
|
|
9
10
|
type Ticker,
|
|
10
11
|
} from 'pixi.js';
|
|
11
12
|
import type { ShellRenderer, ShellHost, OverlayRequest, OverlayHandle } from '@/core/renderer';
|
|
@@ -20,7 +21,6 @@ import { openBuyBonus } from './components/BuyBonus';
|
|
|
20
21
|
import { openBetPicker, openAutoplayPicker } from './components/pickers';
|
|
21
22
|
import { buildModal } from './components/Modal';
|
|
22
23
|
import { buildReplayModal } from './components/ReplayModal';
|
|
23
|
-
import type { ReplayModalOptions } from '@/core/types';
|
|
24
24
|
|
|
25
25
|
export interface PixiRendererOptions {
|
|
26
26
|
app: Application;
|
|
@@ -110,16 +110,17 @@ export class PixiRenderer implements ShellRenderer {
|
|
|
110
110
|
|
|
111
111
|
/** Count a money readout from→to on the freshly-rendered bar's value Text node. Mirrors
|
|
112
112
|
* PixiGameShell.animateMoney (which counted on the just-built bar's value Texts). */
|
|
113
|
-
animateMoney(field: 'balance' | 'win', from: number, to: number): void {
|
|
113
|
+
animateMoney(field: 'balance' | 'win', from: number, to: number, durationMs?: number): void {
|
|
114
114
|
if (!this.bar) return;
|
|
115
|
+
// countUpText defaults durationMs to 450 — pass it only when the caller overrode it.
|
|
116
|
+
const count = (text: Text, fmt: (n: number) => string) =>
|
|
117
|
+
durationMs == null
|
|
118
|
+
? countUpText(this.ticker, text, from, to, fmt)
|
|
119
|
+
: countUpText(this.ticker, text, from, to, fmt, durationMs);
|
|
115
120
|
if (field === 'balance' && this.bar.balanceValue) {
|
|
116
|
-
this.moneyAnims.push(
|
|
117
|
-
countUpText(this.ticker, this.bar.balanceValue, from, to, (n) => this.ctx.fmt(n)),
|
|
118
|
-
);
|
|
121
|
+
this.moneyAnims.push(count(this.bar.balanceValue, (n) => this.ctx.fmt(n)));
|
|
119
122
|
} else if (field === 'win' && this.bar.winValue) {
|
|
120
|
-
this.moneyAnims.push(
|
|
121
|
-
countUpText(this.ticker, this.bar.winValue, from, to, (n) => this.ctx.fmtWin(n)),
|
|
122
|
-
);
|
|
123
|
+
this.moneyAnims.push(count(this.bar.winValue, (n) => this.ctx.fmtWin(n)));
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -147,7 +148,7 @@ export class PixiRenderer implements ShellRenderer {
|
|
|
147
148
|
layer = openAutoplayPicker(this.ctx);
|
|
148
149
|
break;
|
|
149
150
|
case 'replay':
|
|
150
|
-
layer = buildReplayModal(this.ctx, req.opts, () => this.
|
|
151
|
+
layer = buildReplayModal(this.ctx, req.opts, () => this.host.openReplay(req.opts));
|
|
151
152
|
break;
|
|
152
153
|
case 'modal':
|
|
153
154
|
layer = buildModal(this.ctx, req.opts);
|
|
@@ -226,15 +227,6 @@ export class PixiRenderer implements ShellRenderer {
|
|
|
226
227
|
this.currentLayer?.fit?.();
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
/** Replay's reopen path: a replay modal can rebuild itself (e.g. after a replay completes). It is
|
|
230
|
-
* routed back through the controller-equivalent open flow so the layer stack + backdrop stay in
|
|
231
|
-
* sync — mirrors PixiGameShell.openReplay (push a fresh replay modal). */
|
|
232
|
-
private openReplayInternal(opts: ReplayModalOptions): void {
|
|
233
|
-
if (this.destroyed) return;
|
|
234
|
-
const layer = buildReplayModal(this.ctx, opts, () => this.openReplayInternal(opts));
|
|
235
|
-
this.pushLayer(layer);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
230
|
// ── frosted backdrop ─────────────────────────────────────────────────────────
|
|
239
231
|
/** Snapshot the scene behind the modal layer and blur it — the Pixi analogue of the overlay's
|
|
240
232
|
* `backdrop-filter: blur(20px) saturate(120%)`. Static (captured at open time): the game is paused
|
|
@@ -333,6 +325,7 @@ export class PixiRenderer implements ShellRenderer {
|
|
|
333
325
|
get layout() { return host.layout; },
|
|
334
326
|
get soundOn() { return host.soundOn; },
|
|
335
327
|
get actions() { return host.actions; },
|
|
328
|
+
openReplay: (opts) => host.openReplay(opts),
|
|
336
329
|
t: (s) => host.t(s),
|
|
337
330
|
formatCurrency: (n, win) => host.formatCurrency(n, win),
|
|
338
331
|
emit: host.emit.bind(host),
|
package/src/ui/pixi/text.ts
CHANGED
|
@@ -103,6 +103,11 @@ export function makeText(str: string, opts: TextOpts): Text {
|
|
|
103
103
|
if (opts.wrapWidth != null) {
|
|
104
104
|
style.wordWrap = true;
|
|
105
105
|
style.wordWrapWidth = opts.wrapWidth;
|
|
106
|
+
// CJK (and any long unbroken run) has no spaces to wrap on — without breakWords the line
|
|
107
|
+
// overflows the wrap width instead of wrapping. This is the single shell text helper (How to
|
|
108
|
+
// Play, disclaimer, Game Info section titles, mode table, win messages, buy-bonus cards), so
|
|
109
|
+
// one flag fixes wrapping everywhere.
|
|
110
|
+
style.breakWords = true;
|
|
106
111
|
}
|
|
107
112
|
if (opts.lineHeight != null) style.lineHeight = opts.lineHeight;
|
|
108
113
|
if (opts.shadow) {
|