@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.1
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/CHANGELOG.md +9 -0
- package/LICENSE +21 -21
- package/README.md +191 -139
- package/dist/doctor/cli.mjs +49 -19
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.js +1 -1
- package/dist/shared/globals.js +8 -3
- package/dist/shared/globals.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/package.json +89 -89
- package/src/auto.ts +39 -39
- package/src/core/connection.ts +299 -299
- package/src/core/errors.ts +147 -147
- package/src/core/game-data.ts +38 -38
- package/src/core/hub-economy.ts +179 -179
- package/src/core/hub-progress.ts +205 -205
- package/src/core/hub.ts +131 -131
- package/src/core/menu-control.ts +61 -61
- package/src/core/offline/events.ts +50 -50
- package/src/core/offline/favorites.ts +21 -21
- package/src/core/offline/local-store.ts +261 -261
- package/src/core/offline/queue.ts +171 -171
- package/src/core/page.ts +74 -74
- package/src/core/rest/endpoint-types.ts +22 -22
- package/src/core/rest/rest-client.ts +152 -152
- package/src/core/storage/json-guards.ts +74 -74
- package/src/core/storage/key-value.ts +66 -66
- package/src/core/ticket.ts +43 -43
- package/src/core/types.ts +134 -134
- package/src/core/version/client-version.gen.ts +1 -1
- package/src/core/version/hub-version.ts +62 -62
- package/src/core/version/semver.ts +97 -97
- package/src/daily/daily-system.ts +161 -161
- package/src/daily/page-daily.ts +36 -36
- package/src/daily/types.ts +67 -67
- package/src/daily.ts +10 -10
- package/src/doctor/checks.ts +78 -76
- package/src/doctor/cli.ts +98 -106
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -12
- package/src/globals.ts +115 -110
- package/src/index.ts +50 -50
- package/src/input/device-state.ts +146 -146
- package/src/input/edges.ts +55 -55
- package/src/input/gamepad.ts +61 -61
- package/src/input/input-events.ts +32 -32
- package/src/input/input-registry.ts +164 -164
- package/src/input/input-system.ts +393 -393
- package/src/input/legacy-controls.ts +64 -64
- package/src/input/nav-state.ts +191 -191
- package/src/input/navigator.ts +171 -171
- package/src/input/on-screen-controls.ts +101 -101
- package/src/input/page-control.ts +43 -43
- package/src/input/page-input.ts +193 -193
- package/src/input/player-remaps.ts +77 -77
- package/src/input/source-values.ts +116 -116
- package/src/input/spatial.ts +47 -47
- package/src/input/types.ts +331 -331
- package/src/input/zero-config.ts +177 -177
- package/src/input.ts +22 -22
- package/src/mp.ts +10 -10
- package/src/overlay/controls-panel.ts +163 -163
- package/src/overlay/dom.ts +46 -46
- package/src/overlay/layout-clusters.ts +162 -162
- package/src/overlay/layout-geometry.ts +136 -136
- package/src/overlay/layout.ts +203 -203
- package/src/overlay/listener-scope.ts +23 -23
- package/src/overlay/on-screen-keyboard.ts +137 -137
- package/src/overlay/overlay.ts +208 -208
- package/src/overlay/styles.ts +9 -9
- package/src/overlay/virtual-controls.ts +262 -262
- package/src/race.ts +10 -10
- package/src/realtime/bridge.ts +121 -121
- package/src/realtime/cross-bundle.ts +29 -29
- package/src/realtime/fallback-notice.ts +48 -48
- package/src/realtime/hub-link.ts +65 -65
- package/src/realtime/latest-state.ts +33 -33
- package/src/realtime/multiplayer-forwarder.ts +28 -28
- package/src/realtime/multiplayer-types.ts +52 -52
- package/src/realtime/page-runtime.ts +60 -60
- package/src/realtime/race-forwarder.ts +59 -59
- package/src/realtime/race-types.ts +132 -132
- package/src/realtime/room-types.ts +125 -125
- package/src/realtime/rooms-forwarder.ts +79 -79
- package/src/realtime/runtime-api.ts +49 -49
- package/src/realtime/runtime-opener.ts +61 -61
- package/src/realtime/social-forwarder.ts +72 -72
- package/src/realtime/social-types.ts +78 -78
- package/src/realtime/types.ts +53 -53
- package/src/realtime/wire-types.ts +72 -72
- package/src/rooms.ts +10 -10
- package/src/social.ts +18 -18
- package/src/types.ts +13 -13
- package/src/uistack/page-stack.ts +45 -45
- package/src/uistack/play-suspension.ts +82 -82
- package/src/uistack/types.ts +76 -76
- package/src/uistack/ui-stack.ts +144 -144
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The overlay stack is one per page, on `window.__HUB_UI__`, because several
|
|
3
|
-
* bundles share it: the host's `/_hub/hub.js`, a game's bundled client and
|
|
4
|
-
* the social runtime. It has its own handover rule (older than plan §4.4's
|
|
5
|
-
* registry, and kept for v1 copies): a newer implementation replaces an older
|
|
6
|
-
* one only while it is idle, adopting its listeners, so open entries are
|
|
7
|
-
* never orphaned.
|
|
8
|
-
*/
|
|
9
|
-
import { hasMethods, readField } from '../realtime/cross-bundle';
|
|
10
|
-
import type { UiStack } from './types';
|
|
11
|
-
import { UI_STACK_VERSION, UiStackImpl } from './ui-stack';
|
|
12
|
-
|
|
13
|
-
declare global {
|
|
14
|
-
interface Window {
|
|
15
|
-
// eslint-disable-next-line no-restricted-syntax -- trust boundary: set by any bundle on the page, including v1 copies
|
|
16
|
-
__HUB_UI__?: unknown;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Whether `candidate` is some bundle's overlay stack. */
|
|
21
|
-
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the stack may come from another bundle
|
|
22
|
-
export function isUiStack(candidate: unknown): candidate is UiStack {
|
|
23
|
-
return typeof readField(candidate, 'version') === 'number' && hasMethods(candidate, ['push', 'on']);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let pagelessStack: UiStack | null = null;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The page-wide overlay stack, created on first use. Outside a browser a
|
|
30
|
-
* module-local instance is returned.
|
|
31
|
-
*/
|
|
32
|
-
export function getUiStack(): UiStack {
|
|
33
|
-
if (typeof window === 'undefined') {
|
|
34
|
-
pagelessStack ??= new UiStackImpl();
|
|
35
|
-
return pagelessStack;
|
|
36
|
-
}
|
|
37
|
-
const existing = window.__HUB_UI__;
|
|
38
|
-
if (isUiStack(existing) && (existing.version >= UI_STACK_VERSION || existing.isOpen)) {
|
|
39
|
-
return existing;
|
|
40
|
-
}
|
|
41
|
-
const handoff = isUiStack(existing) && hasMethods(existing, ['handoff']) ? existing.handoff() : undefined;
|
|
42
|
-
const stack = new UiStackImpl(handoff);
|
|
43
|
-
window.__HUB_UI__ = stack;
|
|
44
|
-
return stack;
|
|
45
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The overlay stack is one per page, on `window.__HUB_UI__`, because several
|
|
3
|
+
* bundles share it: the host's `/_hub/hub.js`, a game's bundled client and
|
|
4
|
+
* the social runtime. It has its own handover rule (older than plan §4.4's
|
|
5
|
+
* registry, and kept for v1 copies): a newer implementation replaces an older
|
|
6
|
+
* one only while it is idle, adopting its listeners, so open entries are
|
|
7
|
+
* never orphaned.
|
|
8
|
+
*/
|
|
9
|
+
import { hasMethods, readField } from '../realtime/cross-bundle';
|
|
10
|
+
import type { UiStack } from './types';
|
|
11
|
+
import { UI_STACK_VERSION, UiStackImpl } from './ui-stack';
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface Window {
|
|
15
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: set by any bundle on the page, including v1 copies
|
|
16
|
+
__HUB_UI__?: unknown;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Whether `candidate` is some bundle's overlay stack. */
|
|
21
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the stack may come from another bundle
|
|
22
|
+
export function isUiStack(candidate: unknown): candidate is UiStack {
|
|
23
|
+
return typeof readField(candidate, 'version') === 'number' && hasMethods(candidate, ['push', 'on']);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let pagelessStack: UiStack | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The page-wide overlay stack, created on first use. Outside a browser a
|
|
30
|
+
* module-local instance is returned.
|
|
31
|
+
*/
|
|
32
|
+
export function getUiStack(): UiStack {
|
|
33
|
+
if (typeof window === 'undefined') {
|
|
34
|
+
pagelessStack ??= new UiStackImpl();
|
|
35
|
+
return pagelessStack;
|
|
36
|
+
}
|
|
37
|
+
const existing = window.__HUB_UI__;
|
|
38
|
+
if (isUiStack(existing) && (existing.version >= UI_STACK_VERSION || existing.isOpen)) {
|
|
39
|
+
return existing;
|
|
40
|
+
}
|
|
41
|
+
const handoff = isUiStack(existing) && hasMethods(existing, ['handoff']) ? existing.handoff() : undefined;
|
|
42
|
+
const stack = new UiStackImpl(handoff);
|
|
43
|
+
window.__HUB_UI__ = stack;
|
|
44
|
+
return stack;
|
|
45
|
+
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* While hub UI covers the game, its `play` input group is held disabled (so a
|
|
3
|
-
* paddle doesn't move while the player types), even for games that ignore
|
|
4
|
-
* the overlay events. The game's own enable/disable calls for `play` are
|
|
5
|
-
* recorded instead of applied, and the last one wins on release, so a game
|
|
6
|
-
* that pauses itself on `open` stays paused afterwards.
|
|
7
|
-
*/
|
|
8
|
-
import { hasMethods } from '../realtime/cross-bundle';
|
|
9
|
-
|
|
10
|
-
/** The input group the stack suspends. */
|
|
11
|
-
export const PLAY_GROUP = 'play';
|
|
12
|
-
|
|
13
|
-
/** The slice of the page's input system the stack needs (any bundle's). */
|
|
14
|
-
export interface GroupSwitch {
|
|
15
|
-
/** Whether a group is on. */
|
|
16
|
-
isEnabled(group: string): boolean;
|
|
17
|
-
/** Turn a group on. */
|
|
18
|
-
enable(group: string): void;
|
|
19
|
-
/** Turn a group off. */
|
|
20
|
-
disable(group: string): void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Whether `candidate` can switch input groups. */
|
|
24
|
-
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the page's input system may come from another bundle
|
|
25
|
-
export function isGroupSwitch(candidate: unknown): candidate is GroupSwitch {
|
|
26
|
-
return hasMethods(candidate, ['isEnabled', 'enable', 'disable']);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The intercepted method names. */
|
|
30
|
-
const SWITCH_METHODS = ['enable', 'disable'] as const;
|
|
31
|
-
|
|
32
|
-
/** Holds `play` disabled for one overlay visit. */
|
|
33
|
-
export class PlaySuspension {
|
|
34
|
-
private readonly input: GroupSwitch;
|
|
35
|
-
private shouldPlayAfter: boolean;
|
|
36
|
-
/** The own-property descriptors replaced (`undefined` = the method lived on the prototype). */
|
|
37
|
-
private readonly savedMethods: Record<(typeof SWITCH_METHODS)[number], PropertyDescriptor | undefined>;
|
|
38
|
-
|
|
39
|
-
constructor(input: GroupSwitch) {
|
|
40
|
-
this.input = input;
|
|
41
|
-
this.shouldPlayAfter = input.isEnabled(PLAY_GROUP);
|
|
42
|
-
if (this.shouldPlayAfter) {
|
|
43
|
-
input.disable(PLAY_GROUP);
|
|
44
|
-
}
|
|
45
|
-
this.savedMethods = {
|
|
46
|
-
enable: Object.getOwnPropertyDescriptor(input, 'enable'),
|
|
47
|
-
disable: Object.getOwnPropertyDescriptor(input, 'disable'),
|
|
48
|
-
};
|
|
49
|
-
const enableGroup = input.enable.bind(input);
|
|
50
|
-
const disableGroup = input.disable.bind(input);
|
|
51
|
-
// Own-property overrides shadow the real methods until release().
|
|
52
|
-
input.enable = (group: string): void => {
|
|
53
|
-
if (group === PLAY_GROUP) {
|
|
54
|
-
this.shouldPlayAfter = true;
|
|
55
|
-
} else {
|
|
56
|
-
enableGroup(group);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
input.disable = (group: string): void => {
|
|
60
|
-
if (group === PLAY_GROUP) {
|
|
61
|
-
this.shouldPlayAfter = false;
|
|
62
|
-
} else {
|
|
63
|
-
disableGroup(group);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Remove the interception and apply the state the game last asked for. */
|
|
69
|
-
release(): void {
|
|
70
|
-
for (const method of SWITCH_METHODS) {
|
|
71
|
-
const saved = this.savedMethods[method];
|
|
72
|
-
if (saved === undefined) {
|
|
73
|
-
Reflect.deleteProperty(this.input, method);
|
|
74
|
-
} else {
|
|
75
|
-
Object.defineProperty(this.input, method, saved);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (this.shouldPlayAfter) {
|
|
79
|
-
this.input.enable(PLAY_GROUP);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* While hub UI covers the game, its `play` input group is held disabled (so a
|
|
3
|
+
* paddle doesn't move while the player types), even for games that ignore
|
|
4
|
+
* the overlay events. The game's own enable/disable calls for `play` are
|
|
5
|
+
* recorded instead of applied, and the last one wins on release, so a game
|
|
6
|
+
* that pauses itself on `open` stays paused afterwards.
|
|
7
|
+
*/
|
|
8
|
+
import { hasMethods } from '../realtime/cross-bundle';
|
|
9
|
+
|
|
10
|
+
/** The input group the stack suspends. */
|
|
11
|
+
export const PLAY_GROUP = 'play';
|
|
12
|
+
|
|
13
|
+
/** The slice of the page's input system the stack needs (any bundle's). */
|
|
14
|
+
export interface GroupSwitch {
|
|
15
|
+
/** Whether a group is on. */
|
|
16
|
+
isEnabled(group: string): boolean;
|
|
17
|
+
/** Turn a group on. */
|
|
18
|
+
enable(group: string): void;
|
|
19
|
+
/** Turn a group off. */
|
|
20
|
+
disable(group: string): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether `candidate` can switch input groups. */
|
|
24
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the page's input system may come from another bundle
|
|
25
|
+
export function isGroupSwitch(candidate: unknown): candidate is GroupSwitch {
|
|
26
|
+
return hasMethods(candidate, ['isEnabled', 'enable', 'disable']);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The intercepted method names. */
|
|
30
|
+
const SWITCH_METHODS = ['enable', 'disable'] as const;
|
|
31
|
+
|
|
32
|
+
/** Holds `play` disabled for one overlay visit. */
|
|
33
|
+
export class PlaySuspension {
|
|
34
|
+
private readonly input: GroupSwitch;
|
|
35
|
+
private shouldPlayAfter: boolean;
|
|
36
|
+
/** The own-property descriptors replaced (`undefined` = the method lived on the prototype). */
|
|
37
|
+
private readonly savedMethods: Record<(typeof SWITCH_METHODS)[number], PropertyDescriptor | undefined>;
|
|
38
|
+
|
|
39
|
+
constructor(input: GroupSwitch) {
|
|
40
|
+
this.input = input;
|
|
41
|
+
this.shouldPlayAfter = input.isEnabled(PLAY_GROUP);
|
|
42
|
+
if (this.shouldPlayAfter) {
|
|
43
|
+
input.disable(PLAY_GROUP);
|
|
44
|
+
}
|
|
45
|
+
this.savedMethods = {
|
|
46
|
+
enable: Object.getOwnPropertyDescriptor(input, 'enable'),
|
|
47
|
+
disable: Object.getOwnPropertyDescriptor(input, 'disable'),
|
|
48
|
+
};
|
|
49
|
+
const enableGroup = input.enable.bind(input);
|
|
50
|
+
const disableGroup = input.disable.bind(input);
|
|
51
|
+
// Own-property overrides shadow the real methods until release().
|
|
52
|
+
input.enable = (group: string): void => {
|
|
53
|
+
if (group === PLAY_GROUP) {
|
|
54
|
+
this.shouldPlayAfter = true;
|
|
55
|
+
} else {
|
|
56
|
+
enableGroup(group);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
input.disable = (group: string): void => {
|
|
60
|
+
if (group === PLAY_GROUP) {
|
|
61
|
+
this.shouldPlayAfter = false;
|
|
62
|
+
} else {
|
|
63
|
+
disableGroup(group);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Remove the interception and apply the state the game last asked for. */
|
|
69
|
+
release(): void {
|
|
70
|
+
for (const method of SWITCH_METHODS) {
|
|
71
|
+
const saved = this.savedMethods[method];
|
|
72
|
+
if (saved === undefined) {
|
|
73
|
+
Reflect.deleteProperty(this.input, method);
|
|
74
|
+
} else {
|
|
75
|
+
Object.defineProperty(this.input, method, saved);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (this.shouldPlayAfter) {
|
|
79
|
+
this.input.enable(PLAY_GROUP);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/uistack/types.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The overlay stack's public shapes: "is the player looking at hub UI right
|
|
3
|
-
* now?" (`hub.overlay`, docs/multiplayer.md §5.7).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** Why the overlay opened, so a game can word its pause screen. */
|
|
7
|
-
export type OverlayReason = 'menu' | 'chat' | 'dialog' | 'keyboard' | 'invite' | 'notify';
|
|
8
|
-
|
|
9
|
-
/** An overlay edge. */
|
|
10
|
-
export type OverlayEdge = 'open' | 'close';
|
|
11
|
-
|
|
12
|
-
/** Payload of `open` and `close` (and of the `hub:overlay-open` / `hub:overlay-close` window events). */
|
|
13
|
-
export interface OverlayEvent {
|
|
14
|
-
/** The reason of the entry that opened (for `open`) or closed last (for `close`). */
|
|
15
|
-
reason: OverlayReason;
|
|
16
|
-
/**
|
|
17
|
-
* False during a live online match (`hub.mp.setBusy(true)`): the game must
|
|
18
|
-
* keep simulating, so it should not pause.
|
|
19
|
-
*/
|
|
20
|
-
canPause: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Options for {@link UiStack.autoPause}. */
|
|
24
|
-
export interface AutoPauseOptions {
|
|
25
|
-
/** Pause exactly the way the game's own pause does. */
|
|
26
|
-
pause(): void;
|
|
27
|
-
/** Resume play. Only called when `resumeOnClose` is true. */
|
|
28
|
-
resume?(): void;
|
|
29
|
-
/**
|
|
30
|
-
* Resume automatically when the hub UI closes. Default false: action games
|
|
31
|
-
* should show their pause screen so the player resumes deliberately;
|
|
32
|
-
* turn-based and idle games may pass true.
|
|
33
|
-
*/
|
|
34
|
-
resumeOnClose?: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** A listener for overlay edges. */
|
|
38
|
-
export type OverlayListener = (event: OverlayEvent) => void;
|
|
39
|
-
|
|
40
|
-
/** What an older, idle stack hands to a newer one replacing it. */
|
|
41
|
-
export interface UiStackHandoff {
|
|
42
|
-
/** Its listeners, by edge. */
|
|
43
|
-
listeners: { open: OverlayListener[]; close: OverlayListener[] };
|
|
44
|
-
/** Its pause permission. */
|
|
45
|
-
canPause: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The page-wide overlay stack. Every piece of hub UI that covers the game
|
|
50
|
-
* (the menu drawer, a dialog, the chat sheet…) pushes an entry while it shows
|
|
51
|
-
* and pops it when it goes away; the game sees one `open` / `close` pair for
|
|
52
|
-
* the whole visit, however the UI nests.
|
|
53
|
-
*/
|
|
54
|
-
export interface UiStack {
|
|
55
|
-
/** Implementation version; a newer bundle replaces an older, idle instance. */
|
|
56
|
-
readonly version: number;
|
|
57
|
-
/** Register a covering piece of UI. Returns its idempotent `pop()`. */
|
|
58
|
-
push(reason: OverlayReason): () => void;
|
|
59
|
-
/** True while any hub UI covers the game. */
|
|
60
|
-
readonly isOpen: boolean;
|
|
61
|
-
/** Number of open entries (nesting depth). */
|
|
62
|
-
readonly depth: number;
|
|
63
|
-
/** Whether the game may pause right now (false during an online match). */
|
|
64
|
-
readonly canPause: boolean;
|
|
65
|
-
/** Subscribe to an edge. Returns the unsubscribe function. */
|
|
66
|
-
on(edge: OverlayEdge, listener: OverlayListener): () => void;
|
|
67
|
-
/**
|
|
68
|
-
* One-line adoption for games: pause on open (unless `canPause` is false),
|
|
69
|
-
* optionally resume on close. Returns a function that detaches it.
|
|
70
|
-
*/
|
|
71
|
-
autoPause(options: AutoPauseOptions): () => void;
|
|
72
|
-
/** Driven by `hub.mp.setBusy`: false while a live match can't pause. */
|
|
73
|
-
setCanPause(canPause: boolean): void;
|
|
74
|
-
/** Hand listeners to a newer version replacing this (idle) instance. */
|
|
75
|
-
handoff(): UiStackHandoff;
|
|
76
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The overlay stack's public shapes: "is the player looking at hub UI right
|
|
3
|
+
* now?" (`hub.overlay`, docs/multiplayer.md §5.7).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Why the overlay opened, so a game can word its pause screen. */
|
|
7
|
+
export type OverlayReason = 'menu' | 'chat' | 'dialog' | 'keyboard' | 'invite' | 'notify';
|
|
8
|
+
|
|
9
|
+
/** An overlay edge. */
|
|
10
|
+
export type OverlayEdge = 'open' | 'close';
|
|
11
|
+
|
|
12
|
+
/** Payload of `open` and `close` (and of the `hub:overlay-open` / `hub:overlay-close` window events). */
|
|
13
|
+
export interface OverlayEvent {
|
|
14
|
+
/** The reason of the entry that opened (for `open`) or closed last (for `close`). */
|
|
15
|
+
reason: OverlayReason;
|
|
16
|
+
/**
|
|
17
|
+
* False during a live online match (`hub.mp.setBusy(true)`): the game must
|
|
18
|
+
* keep simulating, so it should not pause.
|
|
19
|
+
*/
|
|
20
|
+
canPause: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Options for {@link UiStack.autoPause}. */
|
|
24
|
+
export interface AutoPauseOptions {
|
|
25
|
+
/** Pause exactly the way the game's own pause does. */
|
|
26
|
+
pause(): void;
|
|
27
|
+
/** Resume play. Only called when `resumeOnClose` is true. */
|
|
28
|
+
resume?(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Resume automatically when the hub UI closes. Default false: action games
|
|
31
|
+
* should show their pause screen so the player resumes deliberately;
|
|
32
|
+
* turn-based and idle games may pass true.
|
|
33
|
+
*/
|
|
34
|
+
resumeOnClose?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A listener for overlay edges. */
|
|
38
|
+
export type OverlayListener = (event: OverlayEvent) => void;
|
|
39
|
+
|
|
40
|
+
/** What an older, idle stack hands to a newer one replacing it. */
|
|
41
|
+
export interface UiStackHandoff {
|
|
42
|
+
/** Its listeners, by edge. */
|
|
43
|
+
listeners: { open: OverlayListener[]; close: OverlayListener[] };
|
|
44
|
+
/** Its pause permission. */
|
|
45
|
+
canPause: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The page-wide overlay stack. Every piece of hub UI that covers the game
|
|
50
|
+
* (the menu drawer, a dialog, the chat sheet…) pushes an entry while it shows
|
|
51
|
+
* and pops it when it goes away; the game sees one `open` / `close` pair for
|
|
52
|
+
* the whole visit, however the UI nests.
|
|
53
|
+
*/
|
|
54
|
+
export interface UiStack {
|
|
55
|
+
/** Implementation version; a newer bundle replaces an older, idle instance. */
|
|
56
|
+
readonly version: number;
|
|
57
|
+
/** Register a covering piece of UI. Returns its idempotent `pop()`. */
|
|
58
|
+
push(reason: OverlayReason): () => void;
|
|
59
|
+
/** True while any hub UI covers the game. */
|
|
60
|
+
readonly isOpen: boolean;
|
|
61
|
+
/** Number of open entries (nesting depth). */
|
|
62
|
+
readonly depth: number;
|
|
63
|
+
/** Whether the game may pause right now (false during an online match). */
|
|
64
|
+
readonly canPause: boolean;
|
|
65
|
+
/** Subscribe to an edge. Returns the unsubscribe function. */
|
|
66
|
+
on(edge: OverlayEdge, listener: OverlayListener): () => void;
|
|
67
|
+
/**
|
|
68
|
+
* One-line adoption for games: pause on open (unless `canPause` is false),
|
|
69
|
+
* optionally resume on close. Returns a function that detaches it.
|
|
70
|
+
*/
|
|
71
|
+
autoPause(options: AutoPauseOptions): () => void;
|
|
72
|
+
/** Driven by `hub.mp.setBusy`: false while a live match can't pause. */
|
|
73
|
+
setCanPause(canPause: boolean): void;
|
|
74
|
+
/** Hand listeners to a newer version replacing this (idle) instance. */
|
|
75
|
+
handoff(): UiStackHandoff;
|
|
76
|
+
}
|