@energy8platform/shell 0.4.1 → 0.6.0
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 +223 -69
- package/dist/html.cjs.js.map +1 -1
- package/dist/html.d.ts +56 -5
- package/dist/html.esm.js +223 -69
- package/dist/html.esm.js.map +1 -1
- package/dist/index.cjs.js +176 -42
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +56 -5
- package/dist/index.esm.js +176 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/pixi.cjs.js +392 -88
- package/dist/pixi.cjs.js.map +1 -1
- package/dist/pixi.d.ts +56 -5
- package/dist/pixi.esm.js +392 -88
- package/dist/pixi.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ShellController.ts +204 -43
- package/src/core/renderer.ts +8 -1
- package/src/core/state.ts +11 -0
- package/src/core/types.ts +51 -4
- package/src/core/version.ts +1 -1
- package/src/ui/html/components/BottomBar.ts +71 -32
- package/src/ui/html/components/Settings.ts +14 -5
- package/src/ui/pixi/PixiRenderer.ts +3 -0
- package/src/ui/pixi/components/BottomBar.ts +280 -64
- package/src/ui/pixi/components/Settings.ts +23 -8
- package/src/ui/pixi/primitives/controls.ts +7 -0
package/package.json
CHANGED
|
@@ -6,14 +6,32 @@ import { createI18n, type I18n } from './i18n';
|
|
|
6
6
|
import { KeyboardController, type KeyboardHost } from './keyboard';
|
|
7
7
|
import { PACKAGE_VERSION } from './version';
|
|
8
8
|
import type {
|
|
9
|
-
ShellConfig,
|
|
10
|
-
|
|
9
|
+
ShellConfig,
|
|
10
|
+
ResolvedShellConfig,
|
|
11
|
+
ShellState,
|
|
12
|
+
ShellEvents,
|
|
13
|
+
ShellMode,
|
|
14
|
+
AutoplayOptions,
|
|
15
|
+
FreeSpinsState,
|
|
16
|
+
BonusReadout,
|
|
17
|
+
BonusOption,
|
|
18
|
+
ThemeConfig,
|
|
19
|
+
ModalOptions,
|
|
20
|
+
ReplayModalOptions,
|
|
21
|
+
VolumeKey,
|
|
11
22
|
} from './types';
|
|
12
23
|
import type {
|
|
13
|
-
ShellRenderer,
|
|
24
|
+
ShellRenderer,
|
|
25
|
+
ShellHost,
|
|
26
|
+
ShellActions,
|
|
27
|
+
OverlayHandle,
|
|
28
|
+
OverlayRequest,
|
|
29
|
+
ShellLayoutMode,
|
|
14
30
|
} from './renderer';
|
|
15
31
|
|
|
16
|
-
export interface CreateShellOptions extends ShellConfig {
|
|
32
|
+
export interface CreateShellOptions extends ShellConfig {
|
|
33
|
+
renderer: ShellRenderer;
|
|
34
|
+
}
|
|
17
35
|
|
|
18
36
|
/** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
|
|
19
37
|
export function resolveConfig(config: ShellConfig): ResolvedShellConfig {
|
|
@@ -30,6 +48,7 @@ export function resolveConfig(config: ShellConfig): ResolvedShellConfig {
|
|
|
30
48
|
features: config.features,
|
|
31
49
|
theme: config.theme,
|
|
32
50
|
onBonusBuy: config.onBonusBuy,
|
|
51
|
+
volumes: config.volumes,
|
|
33
52
|
version: config.version ?? '1.0.0',
|
|
34
53
|
isSocial: config.isSocial ?? false,
|
|
35
54
|
replay: config.replay ?? config.mode === 'replay',
|
|
@@ -53,6 +72,7 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
53
72
|
private kbd?: KeyboardController;
|
|
54
73
|
private overlay: OverlayHandle | null = null;
|
|
55
74
|
private soundRefresh: ((on: boolean) => void) | null = null;
|
|
75
|
+
private volumeRefresh: ((key: VolumeKey, value: number) => void) | null = null;
|
|
56
76
|
private prevBalance: number;
|
|
57
77
|
private prevWin: number;
|
|
58
78
|
private destroyed = false;
|
|
@@ -79,12 +99,21 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
79
99
|
}
|
|
80
100
|
|
|
81
101
|
// ── ShellHost ──────────────────────────────────────────────────────────────
|
|
82
|
-
t(text: string): string {
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
t(text: string): string {
|
|
103
|
+
return this.i18n.t(text);
|
|
104
|
+
}
|
|
105
|
+
formatCurrency(n: number, win = false): string {
|
|
106
|
+
return formatCurrency(n, this.config.currency, win);
|
|
107
|
+
}
|
|
108
|
+
formatWin(value: number): string {
|
|
109
|
+
return this.formatCurrency(value, true);
|
|
110
|
+
}
|
|
85
111
|
notifyResize(w: number, h: number): void {
|
|
86
112
|
const layout: ShellLayoutMode = w !== 0 && h > w ? 'mobile' : 'wide';
|
|
87
|
-
if (layout !== this.layout) {
|
|
113
|
+
if (layout !== this.layout) {
|
|
114
|
+
this.layout = layout;
|
|
115
|
+
this.renderer.setLayout(layout);
|
|
116
|
+
}
|
|
88
117
|
this.renderer.renderBar();
|
|
89
118
|
}
|
|
90
119
|
|
|
@@ -94,7 +123,9 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
94
123
|
stepBet: (dir) => {
|
|
95
124
|
const next = stepBet(this.state, dir);
|
|
96
125
|
if (next === this.state.bet) return;
|
|
97
|
-
this.state.bet = next;
|
|
126
|
+
this.state.bet = next;
|
|
127
|
+
this.emit('betChange', next);
|
|
128
|
+
this.renderer.renderBar();
|
|
98
129
|
},
|
|
99
130
|
setBet: (n) => {
|
|
100
131
|
if (n !== this.state.bet) {
|
|
@@ -105,7 +136,9 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
105
136
|
},
|
|
106
137
|
cycleTurbo: () => {
|
|
107
138
|
const next = nextTurbo(this.state.turbo, this.config.features.turbo);
|
|
108
|
-
this.state.turbo = next;
|
|
139
|
+
this.state.turbo = next;
|
|
140
|
+
this.emit('turboChange', next);
|
|
141
|
+
this.renderer.renderBar();
|
|
109
142
|
},
|
|
110
143
|
toggleAutoplay: () => {
|
|
111
144
|
if (this.state.autoplay.active) a.stopAutoplay();
|
|
@@ -113,11 +146,13 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
113
146
|
},
|
|
114
147
|
startAutoplay: (remaining) => {
|
|
115
148
|
this.state.autoplay = { active: true, remaining };
|
|
116
|
-
this.emit('autoplayStart', { active: true, remaining });
|
|
149
|
+
this.emit('autoplayStart', { active: true, remaining });
|
|
150
|
+
this.renderer.renderBar();
|
|
117
151
|
},
|
|
118
152
|
stopAutoplay: () => {
|
|
119
153
|
this.state.autoplay = { active: false, remaining: 0 };
|
|
120
|
-
this.emit('autoplayStop');
|
|
154
|
+
this.emit('autoplayStop');
|
|
155
|
+
this.renderer.renderBar();
|
|
121
156
|
},
|
|
122
157
|
openMenu: () => this.openMenu(),
|
|
123
158
|
openSettings: () => this.openSettings(),
|
|
@@ -138,12 +173,24 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
138
173
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
139
174
|
const self = this;
|
|
140
175
|
const host: KeyboardHost = {
|
|
141
|
-
get state() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
get
|
|
145
|
-
|
|
146
|
-
|
|
176
|
+
get state() {
|
|
177
|
+
return self.state;
|
|
178
|
+
},
|
|
179
|
+
get hotkeysEnabled() {
|
|
180
|
+
return self.config.features.hotkeys !== false;
|
|
181
|
+
},
|
|
182
|
+
get spacebarEnabled() {
|
|
183
|
+
return self.config.features.spacebar !== false;
|
|
184
|
+
},
|
|
185
|
+
get turboLevels() {
|
|
186
|
+
return self.config.features.turbo;
|
|
187
|
+
},
|
|
188
|
+
get autoplayEnabled() {
|
|
189
|
+
return self.config.features.autoplay != null;
|
|
190
|
+
},
|
|
191
|
+
get buyBonusEnabled() {
|
|
192
|
+
return self.config.features.buyBonus !== false;
|
|
193
|
+
},
|
|
147
194
|
hasOpenLayer: () => self.overlay !== null,
|
|
148
195
|
routeToLayer: (e) => self.overlay?.onKey?.(e) ?? false,
|
|
149
196
|
spin: () => self.actions.spin(),
|
|
@@ -160,26 +207,57 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
160
207
|
this.kbd.attach();
|
|
161
208
|
}
|
|
162
209
|
|
|
163
|
-
private pullFocus = (): void => {
|
|
210
|
+
private pullFocus = (): void => {
|
|
211
|
+
try {
|
|
212
|
+
(globalThis as { focus?: () => void }).focus?.();
|
|
213
|
+
} catch {
|
|
214
|
+
/* cross-origin */
|
|
215
|
+
}
|
|
216
|
+
};
|
|
164
217
|
|
|
165
218
|
// ── overlay flow ─────────────────────────────────────────────────────────────
|
|
166
219
|
private show(req: OverlayRequest): void {
|
|
167
220
|
this.closeModal();
|
|
168
221
|
this.overlay = this.renderer.openOverlay(req) ?? null;
|
|
169
222
|
}
|
|
170
|
-
openMenu(): void {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
223
|
+
openMenu(): void {
|
|
224
|
+
this.emit('menuOpen');
|
|
225
|
+
this.openSettings();
|
|
226
|
+
}
|
|
227
|
+
openSettings(): void {
|
|
228
|
+
this.emit('settingsOpen');
|
|
229
|
+
this.show({ kind: 'settings' });
|
|
230
|
+
}
|
|
231
|
+
openInfo(): void {
|
|
232
|
+
this.emit('infoOpen');
|
|
233
|
+
this.show({ kind: 'gameInfo' });
|
|
234
|
+
}
|
|
235
|
+
openBuyBonus(): void {
|
|
236
|
+
if (this.config.onBonusBuy) {
|
|
237
|
+
this.config.onBonusBuy();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.show({ kind: 'buyBonus' });
|
|
241
|
+
}
|
|
242
|
+
openBetPicker(): void {
|
|
243
|
+
this.show({ kind: 'betPicker' });
|
|
244
|
+
}
|
|
245
|
+
openAutoplayPicker(): void {
|
|
246
|
+
this.show({ kind: 'autoplayPicker' });
|
|
247
|
+
}
|
|
248
|
+
openReplay(opts: ReplayModalOptions): void {
|
|
249
|
+
if (this.destroyed) return;
|
|
250
|
+
this.show({ kind: 'replay', opts });
|
|
251
|
+
}
|
|
252
|
+
openModal(opts: ModalOptions): void {
|
|
253
|
+
this.show({ kind: 'modal', opts });
|
|
254
|
+
}
|
|
178
255
|
/** Programmatically dismiss whatever overlay/modal is open. No-op when nothing is shown. */
|
|
179
256
|
closeModal(): void {
|
|
180
257
|
if (!this.overlay) return;
|
|
181
258
|
this.overlay = null;
|
|
182
259
|
this.soundRefresh = null;
|
|
260
|
+
this.volumeRefresh = null;
|
|
183
261
|
this.renderer.closeOverlay();
|
|
184
262
|
}
|
|
185
263
|
|
|
@@ -190,16 +268,39 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
190
268
|
this.soundRefresh?.(on);
|
|
191
269
|
this.renderer.refreshSoundIcon?.(on);
|
|
192
270
|
}
|
|
193
|
-
setSoundRefresh(fn: ((on: boolean) => void) | null): void {
|
|
271
|
+
setSoundRefresh(fn: ((on: boolean) => void) | null): void {
|
|
272
|
+
this.soundRefresh = fn;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── volume ─────────────────────────────────────────────────────────────────
|
|
276
|
+
getVolume(key: VolumeKey): number {
|
|
277
|
+
return this.state.volumes[key];
|
|
278
|
+
}
|
|
279
|
+
/** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
|
|
280
|
+
* clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
|
|
281
|
+
* live-updates the slider if the overlay is currently open. */
|
|
282
|
+
setVolume(key: VolumeKey, value: number): void {
|
|
283
|
+
const v = Math.max(0, Math.min(1, value));
|
|
284
|
+
this.state.volumes[key] = v;
|
|
285
|
+
this.emit('settingChange', { key, value: v });
|
|
286
|
+
this.volumeRefresh?.(key, v);
|
|
287
|
+
}
|
|
288
|
+
setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void {
|
|
289
|
+
this.volumeRefresh = fn;
|
|
290
|
+
}
|
|
194
291
|
|
|
195
292
|
// ── features ─────────────────────────────────────────────────────────────────
|
|
196
293
|
activateFeature(bonus: BonusOption): void {
|
|
197
|
-
this.state.activeFeature = bonus;
|
|
294
|
+
this.state.activeFeature = bonus;
|
|
295
|
+
this.emit('featureActivate', { id: bonus.id });
|
|
296
|
+
this.renderer.renderBar();
|
|
198
297
|
}
|
|
199
298
|
deactivateFeature(): void {
|
|
200
299
|
const prev = this.state.activeFeature;
|
|
201
300
|
if (!prev) return;
|
|
202
|
-
this.state.activeFeature = null;
|
|
301
|
+
this.state.activeFeature = null;
|
|
302
|
+
this.emit('featureDeactivate', { id: prev.id });
|
|
303
|
+
this.renderer.renderBar();
|
|
203
304
|
}
|
|
204
305
|
|
|
205
306
|
// ── game-facing public API (mirrors GameShell/PixiGameShell) ───────────────────
|
|
@@ -207,19 +308,79 @@ export class ShellController extends EventEmitter<ShellEvents> implements ShellH
|
|
|
207
308
|
this.renderer.renderBar();
|
|
208
309
|
if (to !== from) this.renderer.animateMoney(field, from, to);
|
|
209
310
|
}
|
|
210
|
-
setBalance(n: number): void {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
311
|
+
setBalance(n: number): void {
|
|
312
|
+
const from = this.prevBalance;
|
|
313
|
+
this.state.balance = n;
|
|
314
|
+
this.prevBalance = n;
|
|
315
|
+
this.money('balance', from, n);
|
|
316
|
+
}
|
|
317
|
+
setWin(n: number): void {
|
|
318
|
+
const from = this.prevWin;
|
|
319
|
+
this.state.win = n;
|
|
320
|
+
this.prevWin = n;
|
|
321
|
+
this.money('win', from, n);
|
|
322
|
+
}
|
|
323
|
+
setBet(n: number): void {
|
|
324
|
+
this.state.bet = n;
|
|
325
|
+
this.renderer.renderBar();
|
|
326
|
+
}
|
|
327
|
+
setMode(mode: ShellMode): void {
|
|
328
|
+
if (mode === 'replay') this.state.replay = true;
|
|
329
|
+
this.state.mode = mode;
|
|
330
|
+
this.renderer.renderBar();
|
|
331
|
+
}
|
|
332
|
+
setBusy(busy: boolean): void {
|
|
333
|
+
this.state.busy = busy;
|
|
334
|
+
this.renderer.renderBar();
|
|
335
|
+
this.kbd?.notifyBusyChanged(busy);
|
|
336
|
+
}
|
|
337
|
+
setAutoplay(a: AutoplayOptions): void {
|
|
338
|
+
this.state.autoplay = a;
|
|
339
|
+
this.renderer.renderBar();
|
|
340
|
+
}
|
|
341
|
+
setTurbo(level: number): void {
|
|
342
|
+
this.state.turbo = level;
|
|
343
|
+
this.renderer.renderBar();
|
|
344
|
+
}
|
|
345
|
+
setBuyBonusEnabled(enabled: boolean): void {
|
|
346
|
+
this.state.buyBonusEnabled = enabled;
|
|
347
|
+
this.renderer.renderBar();
|
|
348
|
+
}
|
|
349
|
+
setFreeSpins(fs: FreeSpinsState): void {
|
|
350
|
+
this.state.freeSpins = fs;
|
|
351
|
+
this.state.bonus = null;
|
|
352
|
+
this.renderer.renderBar();
|
|
353
|
+
}
|
|
354
|
+
/** Generic bonus readout (adventure / hold-and-spin / respins). Sets a game-supplied label+value
|
|
355
|
+
* override for the bar hero and folds `totalWin` into the shared accumulator. Pairs with
|
|
356
|
+
* `setMode('bonus')`. `setFreeSpins()` clears the override back to the derived current/total. */
|
|
357
|
+
setBonus(b: BonusReadout): void {
|
|
358
|
+
this.state.bonus = { label: b.label, value: b.value };
|
|
359
|
+
this.state.freeSpins = { ...this.state.freeSpins, totalWin: b.totalWin };
|
|
360
|
+
this.renderer.renderBar();
|
|
361
|
+
}
|
|
362
|
+
setTheme(theme: ThemeConfig): void {
|
|
363
|
+
this.config.theme = theme;
|
|
364
|
+
this.tokens = resolveTheme(theme);
|
|
365
|
+
this.renderer.applyTheme(this.tokens);
|
|
366
|
+
this.renderer.renderBar();
|
|
367
|
+
}
|
|
368
|
+
setLanguage(lang: string): void {
|
|
369
|
+
this.config.language = lang;
|
|
370
|
+
this.i18n = createI18n({ language: lang, isSocial: this.config.isSocial });
|
|
371
|
+
this.renderer.renderBar();
|
|
372
|
+
}
|
|
373
|
+
setSocial(isSocial: boolean): void {
|
|
374
|
+
this.config.isSocial = isSocial;
|
|
375
|
+
this.i18n = createI18n({ language: this.config.language, isSocial });
|
|
376
|
+
this.renderer.renderBar();
|
|
377
|
+
}
|
|
378
|
+
setLayout(layout: ShellLayoutMode): void {
|
|
379
|
+
if (layout === this.layout) return;
|
|
380
|
+
this.layout = layout;
|
|
381
|
+
this.renderer.setLayout(layout);
|
|
382
|
+
this.renderer.renderBar();
|
|
383
|
+
}
|
|
223
384
|
|
|
224
385
|
destroy(): Promise<void> {
|
|
225
386
|
if (this.destroyed) return Promise.resolve();
|
package/src/core/renderer.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { EventEmitter } from './EventEmitter';
|
|
|
2
2
|
import type { ShellTokens } from './theme';
|
|
3
3
|
import type {
|
|
4
4
|
ResolvedShellConfig, ShellState, ShellEvents, BonusOption,
|
|
5
|
-
ModalOptions, ReplayModalOptions,
|
|
5
|
+
ModalOptions, ReplayModalOptions, VolumeKey,
|
|
6
6
|
} from './types';
|
|
7
7
|
|
|
8
8
|
export type ShellLayoutMode = 'wide' | 'mobile';
|
|
@@ -77,6 +77,13 @@ export interface ShellHost {
|
|
|
77
77
|
setSound(on: boolean): void;
|
|
78
78
|
/** An open Settings overlay registers an icon updater here (null clears it on close). */
|
|
79
79
|
setSoundRefresh(fn: ((on: boolean) => void) | null): void;
|
|
80
|
+
/** Current volume slider position (0..1) for master/music/sfx. */
|
|
81
|
+
getVolume(key: VolumeKey): number;
|
|
82
|
+
/** Set a volume slider (0..1): clamps, stores, emits `settingChange`, and live-updates an open
|
|
83
|
+
* Settings overlay. Called by the slider control on drag AND by game code as the public API. */
|
|
84
|
+
setVolume(key: VolumeKey, value: number): void;
|
|
85
|
+
/** An open Settings overlay registers a slider updater here (null clears it on close). */
|
|
86
|
+
setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
|
|
80
87
|
/** Logic-bearing actions invoked by renderer controls. */
|
|
81
88
|
readonly actions: ShellActions;
|
|
82
89
|
}
|
package/src/core/state.ts
CHANGED
|
@@ -13,10 +13,21 @@ export function createInitialState(config: ShellConfig): ShellState {
|
|
|
13
13
|
turbo: 0,
|
|
14
14
|
buyBonusEnabled: true,
|
|
15
15
|
freeSpins: { current: 0, total: 0, totalWin: 0 },
|
|
16
|
+
bonus: null,
|
|
16
17
|
activeFeature: null,
|
|
18
|
+
volumes: {
|
|
19
|
+
master: clampVolume(config.volumes?.master),
|
|
20
|
+
music: clampVolume(config.volumes?.music),
|
|
21
|
+
sfx: clampVolume(config.volumes?.sfx),
|
|
22
|
+
},
|
|
17
23
|
};
|
|
18
24
|
}
|
|
19
25
|
|
|
26
|
+
/** Clamp a configured volume to 0..1, defaulting to full (1) when unset/invalid. */
|
|
27
|
+
export function clampVolume(v: number | undefined): number {
|
|
28
|
+
return typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/** Step bet up/down within availableBets, clamped at the ends. */
|
|
21
32
|
export function stepBet(state: ShellState, direction: 1 | -1): number {
|
|
22
33
|
const idx = state.availableBets.indexOf(state.bet);
|
package/src/core/types.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** `freeSpins` and `bonus` are the SAME bar layout (host-driven hero + Total Win); `freeSpins` is
|
|
2
|
+
* kept as a back-compat alias for the common case (its readout is derived current/total), while
|
|
3
|
+
* `bonus` pairs with `setBonus()` to show a game-supplied label + value (adventure, hold-and-spin,
|
|
4
|
+
* respins — anything that isn't a plain free-spins counter). */
|
|
5
|
+
export type ShellMode = 'base' | 'bonus' | 'freeSpins' | 'replay';
|
|
6
|
+
|
|
7
|
+
/** The three independent volume sliders shown in the Settings overlay. */
|
|
8
|
+
export type VolumeKey = 'master' | 'music' | 'sfx';
|
|
9
|
+
export type VolumeLevels = Record<VolumeKey, number>;
|
|
2
10
|
|
|
3
11
|
export interface CurrencyConfig {
|
|
4
12
|
symbol: string;
|
|
@@ -162,6 +170,20 @@ export interface FreeSpinsState {
|
|
|
162
170
|
totalWin: number;
|
|
163
171
|
}
|
|
164
172
|
|
|
173
|
+
/** A game-supplied bonus readout for the bar hero, set via `setBonus()`. Generalizes the
|
|
174
|
+
* free-spins counter: the shell renders `label` (localized via the shell translator, so a game
|
|
175
|
+
* i18n entry for it is honoured) + `value` (a pre-formatted string, shown verbatim — "2 / 10",
|
|
176
|
+
* "3", "×5", "7 coins") + the Total Win accumulator. The shell stays free of any per-game bonus
|
|
177
|
+
* concept — the label/value semantics live in the game/host. */
|
|
178
|
+
export interface BonusReadout {
|
|
179
|
+
/** Bar label, e.g. 'Free spins' | 'Adventure' | 'Hold & Spin'. Run through the shell translator. */
|
|
180
|
+
label: string;
|
|
181
|
+
/** Pre-formatted counter value shown verbatim (NOT translated). */
|
|
182
|
+
value: string;
|
|
183
|
+
/** Cumulative bonus win for the Total Win readout. */
|
|
184
|
+
totalWin: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
165
187
|
/** One footer button of a generic modal. Clicking it runs `on` (if any), then closes the modal. */
|
|
166
188
|
export interface ModalAction {
|
|
167
189
|
title: string;
|
|
@@ -226,12 +248,31 @@ export interface ShellConfig {
|
|
|
226
248
|
* opening the built-in buy-bonus overlay (e.g. the game shows its own bonus UI). The button
|
|
227
249
|
* is shown whenever this OR `features.buyBonus` is set. */
|
|
228
250
|
onBonusBuy?: () => void;
|
|
251
|
+
/** Initial Settings-overlay volume slider positions (each 0..1, defaults to 1 = 100%). The shell
|
|
252
|
+
* keeps them stateful across opens; read/update at runtime via `shell.getVolume()` /
|
|
253
|
+
* `shell.setVolume()`, and listen to `settingChange` ({ key: 'master'|'music'|'sfx' }) to apply. */
|
|
254
|
+
volumes?: Partial<VolumeLevels>;
|
|
229
255
|
}
|
|
230
256
|
|
|
231
257
|
/** ShellConfig after the controller applies defaults (version, isSocial, replay, theme). No mount. */
|
|
232
|
-
export type ResolvedShellConfig = Required<
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
export type ResolvedShellConfig = Required<
|
|
259
|
+
Pick<
|
|
260
|
+
ShellConfig,
|
|
261
|
+
| 'language'
|
|
262
|
+
| 'currency'
|
|
263
|
+
| 'availableBets'
|
|
264
|
+
| 'defaultBet'
|
|
265
|
+
| 'balance'
|
|
266
|
+
| 'win'
|
|
267
|
+
| 'mode'
|
|
268
|
+
| 'features'
|
|
269
|
+
| 'gameInfo'
|
|
270
|
+
| 'version'
|
|
271
|
+
| 'isSocial'
|
|
272
|
+
| 'replay'
|
|
273
|
+
>
|
|
274
|
+
> &
|
|
275
|
+
Pick<ShellConfig, 'currentBet' | 'theme' | 'onBonusBuy' | 'volumes'>;
|
|
235
276
|
|
|
236
277
|
export interface ShellState {
|
|
237
278
|
mode: ShellMode;
|
|
@@ -248,9 +289,15 @@ export interface ShellState {
|
|
|
248
289
|
turbo: number;
|
|
249
290
|
buyBonusEnabled: boolean;
|
|
250
291
|
freeSpins: FreeSpinsState;
|
|
292
|
+
/** Game-supplied bonus label + value override (from `setBonus()`). When null the bar derives the
|
|
293
|
+
* readout from `freeSpins` (label 'Free spins', value current/total) — the back-compat default. */
|
|
294
|
+
bonus: { label: string; value: string } | null;
|
|
251
295
|
/** The currently activated `feature` option (e.g. Ante), or null. Drives the
|
|
252
296
|
* effective-bet readout tint and the BUY BONUS → DISABLE toggle on the bar. */
|
|
253
297
|
activeFeature: BonusOption | null;
|
|
298
|
+
/** Volume slider positions (0..1) surfaced in the Settings overlay. Stateful across opens so a
|
|
299
|
+
* reopened overlay reflects the last-set positions instead of resetting to 100%. */
|
|
300
|
+
volumes: VolumeLevels;
|
|
254
301
|
}
|
|
255
302
|
|
|
256
303
|
export interface ShellEvents {
|
package/src/core/version.ts
CHANGED