@energy8platform/shell 0.8.0 → 0.10.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 +91 -11
- package/dist/html.cjs.js.map +1 -1
- package/dist/html.d.ts +24 -5
- package/dist/html.esm.js +91 -12
- package/dist/html.esm.js.map +1 -1
- package/dist/index.cjs.js +56 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.esm.js +56 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/pixi.cjs.js +136 -30
- package/dist/pixi.cjs.js.map +1 -1
- package/dist/pixi.d.ts +24 -5
- package/dist/pixi.esm.js +136 -31
- package/dist/pixi.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ShellController.ts +10 -2
- package/src/core/device.ts +37 -0
- package/src/core/index.ts +1 -0
- package/src/core/locales.ts +14 -0
- package/src/core/types.ts +7 -2
- package/src/core/version.ts +1 -1
- package/src/ui/html/components/BottomBar.ts +20 -5
- package/src/ui/html/components/GameInfo.ts +8 -3
- package/src/ui/html/shell.css.ts +5 -0
- package/src/ui/pixi/components/BottomBar.ts +22 -3
- package/src/ui/pixi/components/GameInfo.ts +8 -3
- package/src/ui/pixi/primitives/widgets.ts +65 -19
package/dist/pixi.d.ts
CHANGED
|
@@ -305,8 +305,13 @@ interface AutoplayConfig {
|
|
|
305
305
|
}
|
|
306
306
|
interface ShellFeatures {
|
|
307
307
|
turbo: 0 | 1 | 2 | 3;
|
|
308
|
-
/** Master keyboard-shortcut switch
|
|
309
|
-
*
|
|
308
|
+
/** Master keyboard-shortcut switch: `false` disables ALL hotkeys (overrides `spacebar` and any
|
|
309
|
+
* future hotkey) AND hides the Hotkeys section of Game Info, including one the game supplied
|
|
310
|
+
* itself — a keycap chart for keys that do nothing is worse than no chart.
|
|
311
|
+
*
|
|
312
|
+
* Left unset, the shell measures the client (`core/device.ts`): a touchscreen has no keys to
|
|
313
|
+
* press, so it gets neither the shortcuts nor the chart. Set it explicitly when you know better
|
|
314
|
+
* than the media query — which is what the host does with the platform's `device` field. */
|
|
310
315
|
hotkeys?: boolean;
|
|
311
316
|
/** Spacebar starts a spin in base mode. Defaults to `true`; set `false` to disable the
|
|
312
317
|
* keyboard shortcut (e.g. jurisdictions that forbid quick-spin keys). */
|
|
@@ -638,7 +643,7 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
|
|
|
638
643
|
tokens: ShellTokens;
|
|
639
644
|
layout: ShellLayoutMode;
|
|
640
645
|
soundOn: boolean;
|
|
641
|
-
readonly engineVersion = "0.
|
|
646
|
+
readonly engineVersion = "0.10.0";
|
|
642
647
|
readonly actions: ShellActions;
|
|
643
648
|
private renderer;
|
|
644
649
|
private i18n;
|
|
@@ -782,8 +787,22 @@ interface I18n {
|
|
|
782
787
|
}
|
|
783
788
|
declare function createI18n(opts: I18nOptions): I18n;
|
|
784
789
|
|
|
790
|
+
/**
|
|
791
|
+
* The disclaimer body every launch shows, in source English — and the SAME strings that key the
|
|
792
|
+
* translations below. One constant for both on purpose: a host that kept its own copy would drift
|
|
793
|
+
* from these keys by one character and the lookup would miss in silence, leaving a player on any
|
|
794
|
+
* language with English legal text. Certification asks for the opposite ("if multiple languages are
|
|
795
|
+
* supported, the disclaimer is translated and displayed in each language").
|
|
796
|
+
*
|
|
797
|
+
* Brand-free by construction. Stake's own template ends with a seventh line — "TM and © {year}
|
|
798
|
+
* Stake Engine." — and that one belongs to a Stake launch alone: it arrives with the Stake bridge's
|
|
799
|
+
* `INIT.config.disclaimerLines`, which outrank this default, and renders verbatim (see the host's
|
|
800
|
+
* `isBrandLine`). Every other platform gets the same mandated wording without someone else's mark.
|
|
801
|
+
*/
|
|
802
|
+
declare const DISCLAIMER_LINES: readonly string[];
|
|
803
|
+
|
|
785
804
|
/** The @energy8platform/shell package version, stamped into the game-info footer. */
|
|
786
|
-
declare const PACKAGE_VERSION = "0.
|
|
805
|
+
declare const PACKAGE_VERSION = "0.10.0";
|
|
787
806
|
|
|
788
807
|
/** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
|
|
789
808
|
* an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
|
|
@@ -919,5 +938,5 @@ declare function createPixiShell(config: PixiShellConfig): PixiGameShell;
|
|
|
919
938
|
* Resolves when removed — mirrors `removePixiShell` in the legacy package. */
|
|
920
939
|
declare function removePixiShell(): Promise<void>;
|
|
921
940
|
|
|
922
|
-
export { DEFAULT_ACCENT, DEFAULT_MENU, PACKAGE_VERSION, POPOVER, PixiRenderer, SCHEMES, ShellController, createI18n, createPixiShell, createShell, isPresetId, normalizeLang, placePopover, popoverWidth, rangeBounds, removePixiShell, resolveConfig, resolveMenu, resolveTheme, seedMenuValues, socialize };
|
|
941
|
+
export { DEFAULT_ACCENT, DEFAULT_MENU, DISCLAIMER_LINES, PACKAGE_VERSION, POPOVER, PixiRenderer, SCHEMES, ShellController, createI18n, createPixiShell, createShell, isPresetId, normalizeLang, placePopover, popoverWidth, rangeBounds, removePixiShell, resolveConfig, resolveMenu, resolveTheme, seedMenuValues, socialize };
|
|
923
942
|
export type { AutoplayConfig, AutoplayOptions, BonusCardContext, BonusOption, BonusReadout, CellRef, CreateShellOptions, CurrencyConfig, FreeSpinsState, GameInfoContent, GameInfoSection, GameMode, I18n, I18nOptions, Lang, MenuHost, MenuItem, MenuPresetId, MenuRow, ModalAction, ModalOptions, OverlayHandle, OverlayRequest, PaylineDef, PaytableRow, PixiGameShell, PixiShellConfig, PixiShellSurface, PopoverPlacement, Rect as PopoverRect, ReplayModalOptions, ResolvedShellConfig, SafeArea, ShapeDef, Shell, ShellActions, ShellConfig, ShellEvents, ShellFeatures, ShellHost, ShellLayoutMode, ShellMode, ShellRenderer, ShellState, ShellSurface, ShellTokens, ThemeConfig, VolumeKey, VolumeLevels, WinSection };
|
package/dist/pixi.esm.js
CHANGED
|
@@ -1307,6 +1307,19 @@ const D = {
|
|
|
1307
1307
|
L4: 'The game display is not representative of any physical device and is for illustrative purposes only.',
|
|
1308
1308
|
L5: 'Winnings are settled according to the amount received from the Remote Game Server and not from events within the web browser.',
|
|
1309
1309
|
};
|
|
1310
|
+
/**
|
|
1311
|
+
* The disclaimer body every launch shows, in source English — and the SAME strings that key the
|
|
1312
|
+
* translations below. One constant for both on purpose: a host that kept its own copy would drift
|
|
1313
|
+
* from these keys by one character and the lookup would miss in silence, leaving a player on any
|
|
1314
|
+
* language with English legal text. Certification asks for the opposite ("if multiple languages are
|
|
1315
|
+
* supported, the disclaimer is translated and displayed in each language").
|
|
1316
|
+
*
|
|
1317
|
+
* Brand-free by construction. Stake's own template ends with a seventh line — "TM and © {year}
|
|
1318
|
+
* Stake Engine." — and that one belongs to a Stake launch alone: it arrives with the Stake bridge's
|
|
1319
|
+
* `INIT.config.disclaimerLines`, which outrank this default, and renders verbatim (see the host's
|
|
1320
|
+
* `isBrandLine`). Every other platform gets the same mandated wording without someone else's mark.
|
|
1321
|
+
*/
|
|
1322
|
+
const DISCLAIMER_LINES = [D.L1, D.L2, D.L3, D.L4, D.L5];
|
|
1310
1323
|
const DISCLAIMER_LOCALES = {
|
|
1311
1324
|
da: {
|
|
1312
1325
|
[D.L1]: 'Fejlfunktion annullerer alle gevinster og spil.',
|
|
@@ -1690,9 +1703,41 @@ class KeyboardController {
|
|
|
1690
1703
|
}
|
|
1691
1704
|
}
|
|
1692
1705
|
|
|
1706
|
+
/**
|
|
1707
|
+
* Can the player in front of this client actually press a key?
|
|
1708
|
+
*
|
|
1709
|
+
* The shell documents its shortcuts in a Hotkeys section and binds Spacebar to spin. On a phone
|
|
1710
|
+
* neither is reachable, and showing a keycap chart to someone holding a touchscreen is a promise
|
|
1711
|
+
* the game can't keep — a certification lab reads it as a feature offered where it doesn't work.
|
|
1712
|
+
*
|
|
1713
|
+
* The question is deliberately NOT "is the layout narrow" (a portrait desktop window still has a
|
|
1714
|
+
* keyboard) and NOT "is there a touchscreen" (a touch laptop has both). It is: what does the
|
|
1715
|
+
* PRIMARY pointer look like, and can it hover? Coarse-and-hoverless is a touchscreen, and a
|
|
1716
|
+
* touchscreen is the one case where the keys genuinely aren't there.
|
|
1717
|
+
*
|
|
1718
|
+
* A tablet with a keyboard case answers "coarse, no hover" too and loses the chart. That is the
|
|
1719
|
+
* right side to be wrong on: the chart is a convenience, and the keys keep working for anyone who
|
|
1720
|
+
* has them — the media query only decides what the shell ADVERTISES (hosts can still say outright,
|
|
1721
|
+
* via `features.hotkeys`, and the platform's own `device` field does exactly that).
|
|
1722
|
+
*/
|
|
1723
|
+
/** A touchscreen: the primary pointer is a finger, and nothing can hover. */
|
|
1724
|
+
const TOUCH_ONLY = '(pointer: coarse) and (hover: none)';
|
|
1725
|
+
function keyboardCapable(win = typeof window === 'undefined' ? undefined : window) {
|
|
1726
|
+
// No window (SSR, node tests) or a browser too old for matchMedia: assume a keyboard rather than
|
|
1727
|
+
// silently stripping shortcuts from a desktop we simply failed to measure.
|
|
1728
|
+
if (typeof win?.matchMedia !== 'function')
|
|
1729
|
+
return true;
|
|
1730
|
+
try {
|
|
1731
|
+
return !win.matchMedia(TOUCH_ONLY).matches;
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
return true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1693
1738
|
// AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
|
|
1694
1739
|
/** The @energy8platform/shell package version, stamped into the game-info footer. */
|
|
1695
|
-
const PACKAGE_VERSION = '0.
|
|
1740
|
+
const PACKAGE_VERSION = '0.10.0';
|
|
1696
1741
|
|
|
1697
1742
|
/** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
|
|
1698
1743
|
function resolveConfig(config) {
|
|
@@ -1706,7 +1751,10 @@ function resolveConfig(config) {
|
|
|
1706
1751
|
win: config.win,
|
|
1707
1752
|
mode: config.mode,
|
|
1708
1753
|
gameInfo: config.gameInfo,
|
|
1709
|
-
|
|
1754
|
+
// `hotkeys` unset means "decide for me": a touchscreen has no keys to press, so the shell
|
|
1755
|
+
// neither binds them nor advertises them there. A host that knows better — the platform's own
|
|
1756
|
+
// `device` field, a jurisdiction rule — says so outright and that wins. See core/device.ts.
|
|
1757
|
+
features: { ...config.features, hotkeys: config.features.hotkeys ?? keyboardCapable() },
|
|
1710
1758
|
theme: config.theme,
|
|
1711
1759
|
onBonusBuy: config.onBonusBuy,
|
|
1712
1760
|
volumes: config.volumes,
|
|
@@ -1800,7 +1848,11 @@ class ShellController extends EventEmitter {
|
|
|
1800
1848
|
this.renderer.renderBar();
|
|
1801
1849
|
},
|
|
1802
1850
|
toggleAutoplay: () => {
|
|
1803
|
-
|
|
1851
|
+
// A halted run (stopped, but with spins still owed after a lost connection) counts as
|
|
1852
|
+
// "autoplay is on screen": the toggle retires its leftover count, exactly as it stops a
|
|
1853
|
+
// running one. Resuming those spins is the disc's job, not this one's.
|
|
1854
|
+
const { active, remaining } = this.state.autoplay;
|
|
1855
|
+
if (active || remaining > 0)
|
|
1804
1856
|
a.stopAutoplay();
|
|
1805
1857
|
else
|
|
1806
1858
|
this.openAutoplayPicker();
|
|
@@ -3087,6 +3139,7 @@ class SpinDisc extends Container {
|
|
|
3087
3139
|
rotTick;
|
|
3088
3140
|
onSpin;
|
|
3089
3141
|
onStop;
|
|
3142
|
+
onResume;
|
|
3090
3143
|
constructor(opts) {
|
|
3091
3144
|
super();
|
|
3092
3145
|
this.size = opts.size ?? 84;
|
|
@@ -3095,6 +3148,7 @@ class SpinDisc extends Container {
|
|
|
3095
3148
|
this.ticker = opts.ticker;
|
|
3096
3149
|
this.onSpin = opts.onSpin;
|
|
3097
3150
|
this.onStop = opts.onStop;
|
|
3151
|
+
this.onResume = opts.onResume ?? opts.onSpin;
|
|
3098
3152
|
this.disc = new Graphics();
|
|
3099
3153
|
this.glyph = makeIcon('spin', this.glyphSize, this.tokens.btnInk);
|
|
3100
3154
|
this.glyph.position.set((this.size - this.glyphSize) / 2, (this.size - this.glyphSize) / 2);
|
|
@@ -3115,6 +3169,8 @@ class SpinDisc extends Container {
|
|
|
3115
3169
|
return;
|
|
3116
3170
|
if (this.mode === 'stop')
|
|
3117
3171
|
this.onStop();
|
|
3172
|
+
else if (this.mode === 'resume')
|
|
3173
|
+
this.onResume();
|
|
3118
3174
|
else
|
|
3119
3175
|
this.onSpin();
|
|
3120
3176
|
});
|
|
@@ -3126,9 +3182,10 @@ class SpinDisc extends Container {
|
|
|
3126
3182
|
drawDisc(this.disc, this.size, this.tokens.btn, 4);
|
|
3127
3183
|
const glyphColor = hot ? this.tokens.accent : this.tokens.btnInk;
|
|
3128
3184
|
this.glyph.setColor(glyphColor);
|
|
3129
|
-
// the count sits ON the solid (btnInk) STOP square, so it must be light to read
|
|
3185
|
+
// the count sits ON the solid (btnInk) STOP square, so it must be light to read; halted, it
|
|
3186
|
+
// sits on the bare disc instead and takes the disc's own ink.
|
|
3130
3187
|
if (this.countText)
|
|
3131
|
-
this.countText.style.fill = '#ffffff';
|
|
3188
|
+
this.countText.style.fill = this.mode === 'resume' ? this.tokens.btnInk : '#ffffff';
|
|
3132
3189
|
// Disabled (mid-spin / can't spin): darken OPAQUELY (≈ filter:grayscale(.4) brightness(.62))
|
|
3133
3190
|
// with a dark veil over the disc — not alpha, which would let the bright board show through and
|
|
3134
3191
|
// read as a translucent/missing button (what looked "transparent" while spinning).
|
|
@@ -3137,32 +3194,45 @@ class SpinDisc extends Container {
|
|
|
3137
3194
|
this.dim.circle(this.size / 2, this.size / 2, this.size / 2 - 1.5).fill({ color: 0x000000, alpha: 0.4 });
|
|
3138
3195
|
}
|
|
3139
3196
|
}
|
|
3140
|
-
/** STOP glyph + remaining-count
|
|
3141
|
-
setAutoplay(
|
|
3142
|
-
if (
|
|
3143
|
-
this.mode = 'stop';
|
|
3144
|
-
this.stopRotation();
|
|
3145
|
-
// STOP glyph at the disc's full size (like SPIN); the count is centred on top of it.
|
|
3146
|
-
this.glyph.visible = false;
|
|
3147
|
-
this.stopGlyph();
|
|
3148
|
-
if (!this.countText) {
|
|
3149
|
-
this.countText = makeText('', { size: 22, weight: '800', color: '#ffffff', align: 'center', family: NUM_FONT_FAMILY });
|
|
3150
|
-
this.addChild(this.countText); // added after the STOP glyph → renders on top of it
|
|
3151
|
-
}
|
|
3152
|
-
const label = Number.isFinite(remaining) ? String(remaining) : '∞';
|
|
3153
|
-
setText(this.countText, label);
|
|
3154
|
-
this.countText.position.set((this.size - this.countText.width) / 2, (this.size - this.countText.height) / 2);
|
|
3155
|
-
}
|
|
3156
|
-
else {
|
|
3197
|
+
/** STOP glyph + remaining-count while autoplay runs; auto glyph + the same count once it halts. */
|
|
3198
|
+
setAutoplay(mode, remaining) {
|
|
3199
|
+
if (mode === 'off') {
|
|
3157
3200
|
this.mode = 'spin';
|
|
3158
3201
|
this.glyph.visible = true;
|
|
3159
3202
|
this.removeStopGlyph();
|
|
3203
|
+
this.removeAutoGlyph();
|
|
3160
3204
|
if (this.countText) {
|
|
3161
3205
|
this.removeChild(this.countText);
|
|
3162
3206
|
this.countText.destroy();
|
|
3163
3207
|
this.countText = undefined;
|
|
3164
3208
|
}
|
|
3209
|
+
this.paint();
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
const running = mode === 'running';
|
|
3213
|
+
this.mode = running ? 'stop' : 'resume';
|
|
3214
|
+
this.stopRotation();
|
|
3215
|
+
this.glyph.visible = false;
|
|
3216
|
+
// Running: a solid STOP square at the disc's full size (like SPIN), count centred on top of it.
|
|
3217
|
+
// Halted: the auto glyph, smaller and lifted, with the count under it — no dark square, because
|
|
3218
|
+
// nothing is running to stop, and a white count would have nothing to read against.
|
|
3219
|
+
if (running) {
|
|
3220
|
+
this.removeAutoGlyph();
|
|
3221
|
+
this.stopGlyph();
|
|
3222
|
+
}
|
|
3223
|
+
else {
|
|
3224
|
+
this.removeStopGlyph();
|
|
3225
|
+
this.autoGlyph();
|
|
3165
3226
|
}
|
|
3227
|
+
if (!this.countText) {
|
|
3228
|
+
this.countText = makeText('', { size: 22, weight: '800', color: '#ffffff', align: 'center', family: NUM_FONT_FAMILY });
|
|
3229
|
+
this.addChild(this.countText); // added after the glyph → renders on top of it
|
|
3230
|
+
}
|
|
3231
|
+
setText(this.countText, Number.isFinite(remaining) ? String(remaining) : '∞');
|
|
3232
|
+
const cy = running
|
|
3233
|
+
? (this.size - this.countText.height) / 2
|
|
3234
|
+
: this.size * 0.56; // sits under the lifted auto glyph
|
|
3235
|
+
this.countText.position.set((this.size - this.countText.width) / 2, cy);
|
|
3166
3236
|
this.paint();
|
|
3167
3237
|
}
|
|
3168
3238
|
stopGlyphView;
|
|
@@ -3180,6 +3250,22 @@ class SpinDisc extends Container {
|
|
|
3180
3250
|
this.stopGlyphView = undefined;
|
|
3181
3251
|
}
|
|
3182
3252
|
}
|
|
3253
|
+
autoGlyphView;
|
|
3254
|
+
autoGlyph() {
|
|
3255
|
+
if (this.autoGlyphView)
|
|
3256
|
+
return;
|
|
3257
|
+
const size = this.glyphSize * 0.42;
|
|
3258
|
+
this.autoGlyphView = makeIcon('autoplay', size, this.tokens.btnInk);
|
|
3259
|
+
this.autoGlyphView.position.set((this.size - size) / 2, this.size * 0.2);
|
|
3260
|
+
this.addChild(this.autoGlyphView);
|
|
3261
|
+
}
|
|
3262
|
+
removeAutoGlyph() {
|
|
3263
|
+
if (this.autoGlyphView) {
|
|
3264
|
+
this.removeChild(this.autoGlyphView);
|
|
3265
|
+
this.autoGlyphView.destroy();
|
|
3266
|
+
this.autoGlyphView = undefined;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3183
3269
|
setBusy(busy) {
|
|
3184
3270
|
this._busy = busy;
|
|
3185
3271
|
if (busy && this.mode === 'spin')
|
|
@@ -3410,6 +3496,13 @@ function divider(tokens, height = 30) {
|
|
|
3410
3496
|
}
|
|
3411
3497
|
|
|
3412
3498
|
// ── design constants (mirror the DOM `.ge-bar-panel` / mobile rules) ──────────
|
|
3499
|
+
/** Which of the disc's three faces the autoplay state calls for — see `SpinAutoplayMode`. A run
|
|
3500
|
+
* that halted with spins still owed (`!active && remaining > 0`) keeps its counter on the disc. */
|
|
3501
|
+
function autoplayDiscMode(state) {
|
|
3502
|
+
if (state.autoplay.active)
|
|
3503
|
+
return 'running';
|
|
3504
|
+
return state.autoplay.remaining > 0 ? 'paused' : 'off';
|
|
3505
|
+
}
|
|
3413
3506
|
const BAR_H = 68; // continuous dark panel height
|
|
3414
3507
|
const SPIN = 84; // hero disc — pops above/below the bar
|
|
3415
3508
|
const SPIN_POP = (SPIN - BAR_H) / 2; // 8 — how far the disc sticks out top/bottom
|
|
@@ -3658,9 +3751,9 @@ class BottomBar extends Container {
|
|
|
3658
3751
|
ticker: this.host.ticker,
|
|
3659
3752
|
onSpin: () => this.host.actions.spin(),
|
|
3660
3753
|
onStop: () => this.stopAutoplay(),
|
|
3754
|
+
onResume: () => this.resumeAutoplay(),
|
|
3661
3755
|
});
|
|
3662
|
-
|
|
3663
|
-
this.spin.setAutoplay(true, state.autoplay.remaining);
|
|
3756
|
+
this.spin.setAutoplay(autoplayDiscMode(state), state.autoplay.remaining);
|
|
3664
3757
|
if (state.busy)
|
|
3665
3758
|
this.spin.setBusy(true);
|
|
3666
3759
|
spinWrap.add(this.spin);
|
|
@@ -3732,9 +3825,9 @@ class BottomBar extends Container {
|
|
|
3732
3825
|
ticker: this.host.ticker,
|
|
3733
3826
|
onSpin: () => this.host.actions.spin(),
|
|
3734
3827
|
onStop: () => this.stopAutoplay(),
|
|
3828
|
+
onResume: () => this.resumeAutoplay(),
|
|
3735
3829
|
});
|
|
3736
|
-
|
|
3737
|
-
this.spin.setAutoplay(true, state.autoplay.remaining);
|
|
3830
|
+
this.spin.setAutoplay(autoplayDiscMode(state), state.autoplay.remaining);
|
|
3738
3831
|
if (state.busy)
|
|
3739
3832
|
this.spin.setBusy(true);
|
|
3740
3833
|
hero = this.spin;
|
|
@@ -3929,8 +4022,11 @@ class BottomBar extends Container {
|
|
|
3929
4022
|
onTurbo() {
|
|
3930
4023
|
this.host.actions.cycleTurbo();
|
|
3931
4024
|
}
|
|
4025
|
+
/** Same button, three jobs: stop a running run, retire a halted run's leftover count (freeing the
|
|
4026
|
+
* disc for a manual spin again), or open the picker. Mirrors the DOM bar's `onAutoplay`. */
|
|
3932
4027
|
onAutoplay() {
|
|
3933
|
-
|
|
4028
|
+
const { active, remaining } = this.host.state.autoplay;
|
|
4029
|
+
if (active || remaining > 0)
|
|
3934
4030
|
this.stopAutoplay();
|
|
3935
4031
|
else
|
|
3936
4032
|
this.host.actions.openAutoplayPicker();
|
|
@@ -3938,6 +4034,10 @@ class BottomBar extends Container {
|
|
|
3938
4034
|
stopAutoplay() {
|
|
3939
4035
|
this.host.actions.stopAutoplay();
|
|
3940
4036
|
}
|
|
4037
|
+
/** Halted run, tapped: play out the spins it still owes. */
|
|
4038
|
+
resumeAutoplay() {
|
|
4039
|
+
this.host.actions.startAutoplay(this.host.state.autoplay.remaining);
|
|
4040
|
+
}
|
|
3941
4041
|
betLocked() {
|
|
3942
4042
|
return this.host.state.busy || this.host.state.autoplay.active;
|
|
3943
4043
|
}
|
|
@@ -4824,10 +4924,15 @@ function openGameInfo(host) {
|
|
|
4824
4924
|
}
|
|
4825
4925
|
function buildBody(host, width) {
|
|
4826
4926
|
const col = new FlexBox({ direction: 'column', align: 'stretch', gap: 12 });
|
|
4827
|
-
const
|
|
4828
|
-
// Auto-inject a hotkeys section unless the game already provides one
|
|
4927
|
+
const allSections = host.config.gameInfo.sections ?? [];
|
|
4928
|
+
// Auto-inject a hotkeys section unless the game already provides one. With hotkeys off — a
|
|
4929
|
+
// jurisdiction that forbids them, or a touchscreen that has no keys at all (see core/device.ts) —
|
|
4930
|
+
// there is no keyboard surface to document, and a game-supplied section is dropped along with the
|
|
4931
|
+
// auto-injected one: a keycap chart for keys the player cannot press is a promise the game breaks.
|
|
4932
|
+
const keys = host.config.features.hotkeys !== false;
|
|
4933
|
+
const rawSections = keys ? allSections : allSections.filter((s) => s.type !== 'hotkeys');
|
|
4829
4934
|
const sectionsWithHotkeys = [...rawSections];
|
|
4830
|
-
if (
|
|
4935
|
+
if (keys && !rawSections.some((s) => s.type === 'hotkeys')) {
|
|
4831
4936
|
sectionsWithHotkeys.push({ type: 'hotkeys', order: HOTKEYS_DEFAULT_ORDER });
|
|
4832
4937
|
}
|
|
4833
4938
|
const sections = sectionsWithHotkeys;
|
|
@@ -7091,5 +7196,5 @@ function removePixiShell() {
|
|
|
7091
7196
|
return shell.destroy();
|
|
7092
7197
|
}
|
|
7093
7198
|
|
|
7094
|
-
export { DEFAULT_ACCENT, DEFAULT_MENU, PACKAGE_VERSION, POPOVER, PixiRenderer, SCHEMES, ShellController, createI18n, createPixiShell, createShell, isPresetId, normalizeLang, placePopover, popoverWidth, rangeBounds, removePixiShell, resolveConfig, resolveMenu, resolveTheme, seedMenuValues, socialize };
|
|
7199
|
+
export { DEFAULT_ACCENT, DEFAULT_MENU, DISCLAIMER_LINES, PACKAGE_VERSION, POPOVER, PixiRenderer, SCHEMES, ShellController, createI18n, createPixiShell, createShell, isPresetId, normalizeLang, placePopover, popoverWidth, rangeBounds, removePixiShell, resolveConfig, resolveMenu, resolveTheme, seedMenuValues, socialize };
|
|
7095
7200
|
//# sourceMappingURL=pixi.esm.js.map
|