@idosgames/module-sdk 0.10.1 → 0.12.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/index.cjs +1 -1
- package/dist/index.d.cts +158 -6
- package/dist/index.d.ts +158 -6
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var x=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function S(e){return typeof e=="string"&&x.test(e)}function
|
|
1
|
+
'use strict';var x=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function S(e){return typeof e=="string"&&x.test(e)}function M(e){let t=e.indexOf(":");return t<0?"":e.slice(0,t)}var c=/^(string|number|boolean)(\[\])?(\?)?$/;function u(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function l(e,t){return e==="number"?typeof t=="number"&&Number.isFinite(t):typeof t===e}function f(e,t,r){if(!u(t))return `${r||"payload"} must be an object`;for(let[n,o]of Object.entries(e)){let i=r?`${r}.${n}`:n,a=t[n];if(typeof o!="string"){let p=f(o,a,i);if(p)return p;continue}let d=c.exec(o);if(!d)return `${i}: invalid descriptor "${o}"`;let[,s="",g,m]=d;if(a===void 0){if(m)continue;return `${i} is missing`}if(g){if(!Array.isArray(a))return `${i} must be ${s}[]`;if(!a.every(p=>l(s,p)))return `${i} must contain only ${s} values`;continue}if(!l(s,a))return `${i} must be a ${s}`}return null}function y(e,t){for(let[r,n]of Object.entries(e)){let o=t?`${t}.${r}`:r;if(typeof n=="string"){if(!c.test(n))throw new Error(`shape(): field "${o}" has invalid type "${n}" \u2014 use "string" | "number" | "boolean", optionally with "[]" and/or a trailing "?"`)}else if(u(n))y(n,o);else throw new Error(`shape(): field "${o}" must be a type string or an object`)}}function h(e){return y(e,""),{descriptor:e,check:t=>f(e,t,"")}}function v(e,t){if(!S(e))throw new Error(`defineTopic(): "${e}" is not a valid topic name \u2014 expected "<module-id>:<event>@<major>", e.g. "idle-rpg:character-upgraded@1"`);return {name:e,shape:t}}var T={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var b=["currency-bar","wallet","status","account","menu","modes"];function R(e){return typeof e=="string"&&b.includes(e)}function D(e){return e}exports.SHARED_UI_ROLES=b;exports.TOPIC_NAME_RE=x;exports.defineModule=D;exports.defineTopic=v;exports.hostTopics=T;exports.isSharedUiRole=R;exports.isTopicName=S;exports.shape=h;exports.topicNamespace=M;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RealtimeSession, IDosGamesClient } from '@idosgames/core';
|
|
1
|
+
import { RealtimeSession, VoiceController, IDosGamesClient } from '@idosgames/core';
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
@@ -217,8 +217,12 @@ interface FeatureScreenProps {
|
|
|
217
217
|
interface FeatureDefinition {
|
|
218
218
|
/** Unique across the game — conventionally the owning module's id ("store"). */
|
|
219
219
|
id: string;
|
|
220
|
-
/**
|
|
221
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Menu label in the player's language. Prefer a function (`label: () => t("title")`): the lobby
|
|
222
|
+
* calls it on every render, so the tab follows the language switch (UI kit `setLanguage`) without
|
|
223
|
+
* a reload. A plain string stays as registered.
|
|
224
|
+
*/
|
|
225
|
+
label: string | (() => string);
|
|
222
226
|
/** Glyph name of @idosgames/react/ui ("shop", "hero"…), an emoji, or an image URL. */
|
|
223
227
|
icon?: string;
|
|
224
228
|
group?: FeatureGroup;
|
|
@@ -236,7 +240,9 @@ interface FeatureDefinition {
|
|
|
236
240
|
available?: boolean;
|
|
237
241
|
}
|
|
238
242
|
/** A registered feature plus its live badge and availability. */
|
|
239
|
-
interface FeatureEntry extends FeatureDefinition {
|
|
243
|
+
interface FeatureEntry extends Omit<FeatureDefinition, "label"> {
|
|
244
|
+
/** The label now — a function label is called on every read. */
|
|
245
|
+
readonly label: string;
|
|
240
246
|
badge: number;
|
|
241
247
|
/** The module that registered it. */
|
|
242
248
|
moduleId: string;
|
|
@@ -339,6 +345,13 @@ interface MultiplayerRoom {
|
|
|
339
345
|
readonly lateJoin: boolean;
|
|
340
346
|
/** Leave the room for good: closes the session and the rooms screen forgets it. */
|
|
341
347
|
leave(): Promise<void>;
|
|
348
|
+
/**
|
|
349
|
+
* The room's voice chat, or `null` when the room has none. The rooms system owns it and draws its
|
|
350
|
+
* controls (the mic button over the game included); a game reads it for its own touches — who is
|
|
351
|
+
* speaking (`isSpeaking`, `on("change")`), a talking player's marker, proximity volume
|
|
352
|
+
* (`setPeerVolume` by distance).
|
|
353
|
+
*/
|
|
354
|
+
readonly voice: VoiceController | null;
|
|
342
355
|
}
|
|
343
356
|
/** What `ctx.features.open("multiplayer", args)` understands. */
|
|
344
357
|
interface MultiplayerScreenArgs {
|
|
@@ -357,6 +370,84 @@ interface MultiplayerRegistry {
|
|
|
357
370
|
subscribe(listener: () => void): () => void;
|
|
358
371
|
}
|
|
359
372
|
|
|
373
|
+
/** A button under the showcase or on the "Play" card. */
|
|
374
|
+
interface LobbyHeroAction {
|
|
375
|
+
id: string;
|
|
376
|
+
/** Already in the player's language. */
|
|
377
|
+
label: string;
|
|
378
|
+
/** Glyph name of @idosgames/react/ui ("edit", "star"…), an emoji, or an image URL. */
|
|
379
|
+
icon?: string;
|
|
380
|
+
/** Pressed. Omit when the button only opens `menu`. */
|
|
381
|
+
run?(): void;
|
|
382
|
+
/** A short list shown when the button is pressed (an emote picker) — each entry runs on its own. */
|
|
383
|
+
menu?: LobbyHeroAction[];
|
|
384
|
+
disabled?: boolean;
|
|
385
|
+
}
|
|
386
|
+
/** What the lobby lays out around the showcase. Read through `view()`, redrawn on `subscribe`. */
|
|
387
|
+
interface LobbyHeroView {
|
|
388
|
+
/** Big name under the showcase (the character's name). */
|
|
389
|
+
title?: string;
|
|
390
|
+
/** A line under it. */
|
|
391
|
+
subtitle?: string;
|
|
392
|
+
/** Buttons under the name ("Edit", "Emote"). */
|
|
393
|
+
actions?: LobbyHeroAction[];
|
|
394
|
+
/**
|
|
395
|
+
* The card next to "Play" — WHAT will be played: a caption ("World"), a name ("Island"), buttons
|
|
396
|
+
* to change it ("Change", "New"). Omitted — "Play" alone.
|
|
397
|
+
*/
|
|
398
|
+
play?: {
|
|
399
|
+
caption?: string;
|
|
400
|
+
title?: string;
|
|
401
|
+
actions?: LobbyHeroAction[];
|
|
402
|
+
/** Overflow menu of the card ("⋯": download, upload…). */
|
|
403
|
+
more?: LobbyHeroAction[];
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/** Handed to `mount`: where on the host the main figure should stand. */
|
|
407
|
+
interface LobbyHeroStage {
|
|
408
|
+
/**
|
|
409
|
+
* The free spot between the lobby's interface where the figure should stand, in CSS pixels of the
|
|
410
|
+
* host element (it moves with the layout — read it every frame). `null` — not laid out yet.
|
|
411
|
+
*/
|
|
412
|
+
spot(): {
|
|
413
|
+
x: number;
|
|
414
|
+
y: number;
|
|
415
|
+
w: number;
|
|
416
|
+
h: number;
|
|
417
|
+
} | null;
|
|
418
|
+
}
|
|
419
|
+
interface LobbyHeroDefinition {
|
|
420
|
+
/** The game mode "Play" starts (a route id of the game). */
|
|
421
|
+
mode: string;
|
|
422
|
+
/**
|
|
423
|
+
* Put the showcase into `host` — an element filling the lobby's home behind its interface. Called
|
|
424
|
+
* when the home is shown, the returned function when it is left (stop the render loop there). The
|
|
425
|
+
* same hero may be mounted again later.
|
|
426
|
+
*/
|
|
427
|
+
mount(host: HTMLElement, stage: LobbyHeroStage): () => void;
|
|
428
|
+
/** The current texts and buttons. Cheap — called on every redraw. */
|
|
429
|
+
view(): LobbyHeroView;
|
|
430
|
+
/** Called by the hero whenever `view()` would answer differently. Returns the unsubscribe. */
|
|
431
|
+
subscribe(listener: () => void): () => void;
|
|
432
|
+
/**
|
|
433
|
+
* "Play" was pressed: return false to stay in the lobby (nothing to play yet). Default: switch to
|
|
434
|
+
* `mode`.
|
|
435
|
+
*/
|
|
436
|
+
beforePlay?(): boolean;
|
|
437
|
+
}
|
|
438
|
+
/** `ctx.lobby` — the app's lobby as seen by a module. */
|
|
439
|
+
interface LobbyRegistry {
|
|
440
|
+
/** Put this game's showcase on the lobby's home. Returns the unregister function. */
|
|
441
|
+
setHero(hero: LobbyHeroDefinition): () => void;
|
|
442
|
+
/** The hero on the home now (the lobby reads it). */
|
|
443
|
+
hero(): LobbyHeroDefinition | null;
|
|
444
|
+
subscribe(listener: () => void): () => void;
|
|
445
|
+
/** Whether the app has a lobby to return to (the base is installed). */
|
|
446
|
+
available(): boolean;
|
|
447
|
+
/** Go to the lobby — a game's "To the lobby" in its pause menu. False — there is no lobby. */
|
|
448
|
+
open(): boolean;
|
|
449
|
+
}
|
|
450
|
+
|
|
360
451
|
/** One piece of work on the loading screen. Call `done()` exactly once, also on failure. */
|
|
361
452
|
interface LoadingTask {
|
|
362
453
|
/**
|
|
@@ -441,6 +532,32 @@ interface AppNotifications {
|
|
|
441
532
|
dismiss(id: string): void;
|
|
442
533
|
}
|
|
443
534
|
|
|
535
|
+
/** The room's voices, as the recording takes them (the multiplayer system provides this). */
|
|
536
|
+
interface RecordingVoiceSource {
|
|
537
|
+
/** This player's microphone track (silent while not transmitting); null — mic off. */
|
|
538
|
+
mic(): MediaStreamTrack | null;
|
|
539
|
+
/** Other players' voice tracks. */
|
|
540
|
+
others(): MediaStreamTrack[];
|
|
541
|
+
/** The publisher allows other players' voices in videos (`Multiplayer.RecordVoices`). */
|
|
542
|
+
recordOthers(): boolean;
|
|
543
|
+
/** Called when tracks come or go (a player joined, the mic went on). */
|
|
544
|
+
subscribe(listener: () => void): () => void;
|
|
545
|
+
}
|
|
546
|
+
/** `ctx.clips`. */
|
|
547
|
+
interface ClipsRegistry {
|
|
548
|
+
/**
|
|
549
|
+
* A bright moment right now. `weight` 1–10 (default 5): how much it is worth showing — a boss
|
|
550
|
+
* kill 8, a level-up 4. Close marks merge into one clip (their weights add up).
|
|
551
|
+
*/
|
|
552
|
+
mark(name: string, weight?: number): void;
|
|
553
|
+
/** The multiplayer system registers the room's voices here; `null` when the room is gone. */
|
|
554
|
+
setVoiceSource(source: RecordingVoiceSource | null): void;
|
|
555
|
+
/** This player is making a long recording right now. */
|
|
556
|
+
isRecording(): boolean;
|
|
557
|
+
/** Called when `isRecording` changes. */
|
|
558
|
+
subscribe(listener: () => void): () => void;
|
|
559
|
+
}
|
|
560
|
+
|
|
444
561
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
445
562
|
type ModuleType = "game" | "app" | "ai-app";
|
|
446
563
|
/**
|
|
@@ -629,6 +746,12 @@ interface ModuleContext {
|
|
|
629
746
|
* builds its "Play" buttons from it; a game can tell whether a lobby exists to return to.
|
|
630
747
|
*/
|
|
631
748
|
modes: ModeRegistry;
|
|
749
|
+
/**
|
|
750
|
+
* The app's lobby: a game puts its showcase on the lobby's home (`setHero` — its character on a
|
|
751
|
+
* pedestal, "Play" next to it) and sends the player back to it from its pause menu (`open`). See
|
|
752
|
+
* `LobbyHeroDefinition`.
|
|
753
|
+
*/
|
|
754
|
+
lobby: LobbyRegistry;
|
|
632
755
|
/**
|
|
633
756
|
* Switch the host to another mode (a route id registered by any module) — e.g. the lobby starts a
|
|
634
757
|
* game, the Workshop opens a world and hands the screen to the game. Unknown ids are ignored. Called
|
|
@@ -660,6 +783,12 @@ interface ModuleContext {
|
|
|
660
783
|
* one look, one queue, one sound for the whole app. The module's own id is the default `source`.
|
|
661
784
|
*/
|
|
662
785
|
notifications: AppNotifications;
|
|
786
|
+
/**
|
|
787
|
+
* The platform's video recording (see `ClipsRegistry`): a game marks bright moments
|
|
788
|
+
* (`ctx.clips.mark("boss_down", 8)`) — they become the player's "Best moments" clips on
|
|
789
|
+
* idosgames.com. A no-op anywhere else.
|
|
790
|
+
*/
|
|
791
|
+
clips: ClipsRegistry;
|
|
663
792
|
}
|
|
664
793
|
/**
|
|
665
794
|
* A module's debug surface for the agent: read its state, drive it with named actions.
|
|
@@ -752,6 +881,21 @@ interface EngineScene {
|
|
|
752
881
|
* host falls back to reading the canvas itself, which is correct for 2D canvases.
|
|
753
882
|
*/
|
|
754
883
|
capture?(): Promise<Blob | null>;
|
|
884
|
+
/**
|
|
885
|
+
* Where the action is right now, 0–1 on both axes of the canvas (the player, the explosion) — for
|
|
886
|
+
* the platform's video recording: a vertical (TikTok) cut of a landscape game follows this point
|
|
887
|
+
* instead of cutting the middle. Called a few times a second; keep it cheap. Omit — the centre.
|
|
888
|
+
*/
|
|
889
|
+
focus?(): {
|
|
890
|
+
x: number;
|
|
891
|
+
y: number;
|
|
892
|
+
} | null;
|
|
893
|
+
/**
|
|
894
|
+
* The game's own sound as a MediaStream, for the platform's video recording — e.g. a
|
|
895
|
+
* `MediaStreamAudioDestinationNode` your audio graph also feeds (Three.js: `listener.context`).
|
|
896
|
+
* Omit — videos carry the voices but not the game's sound.
|
|
897
|
+
*/
|
|
898
|
+
audio?(): MediaStream | null;
|
|
755
899
|
/** Permanent teardown — dispose GPU resources, remove listeners. */
|
|
756
900
|
destroy(): void;
|
|
757
901
|
}
|
|
@@ -790,9 +934,15 @@ interface RouteEntry {
|
|
|
790
934
|
* A game that lives INSIDE the lobby: the base keeps the lobby's balances on top and its tabs at
|
|
791
935
|
* the bottom around it, "Play" is this game, and the app starts in it after sign-in (idle-rpg).
|
|
792
936
|
* Without it (the default) a game takes the whole screen, launched by "Play" in the lobby, with a
|
|
793
|
-
* small button back (
|
|
937
|
+
* small button back (board-game).
|
|
794
938
|
*/
|
|
795
939
|
inLobby?: boolean;
|
|
940
|
+
/**
|
|
941
|
+
* A full-screen game with its OWN way back to the lobby (a pause menu with "To the lobby" that
|
|
942
|
+
* calls `ctx.lobby.open()` — voxelcraft): the base does not draw its round button back over it.
|
|
943
|
+
* The game must then offer that way on every device (a pause button on touch screens).
|
|
944
|
+
*/
|
|
945
|
+
ownExit?: boolean;
|
|
796
946
|
}
|
|
797
947
|
/** A mode of the app as every module sees it (`ctx.modes`): a registered route plus its owner. */
|
|
798
948
|
interface ModeEntry {
|
|
@@ -807,6 +957,8 @@ interface ModeEntry {
|
|
|
807
957
|
moduleType: ModuleType;
|
|
808
958
|
/** The route's `inLobby`: the game sits inside the lobby rather than taking the whole screen. */
|
|
809
959
|
inLobby?: boolean;
|
|
960
|
+
/** The route's `ownExit`: the game leads back to the lobby itself — no button back over it. */
|
|
961
|
+
ownExit?: boolean;
|
|
810
962
|
}
|
|
811
963
|
/** `ctx.modes` — the modes of the app, live. Switch with `ctx.navigate(id)`. */
|
|
812
964
|
interface ModeRegistry {
|
|
@@ -832,4 +984,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
832
984
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
833
985
|
declare function defineModule(module: Module): Module;
|
|
834
986
|
|
|
835
|
-
export { type AppNotifications, type CapturedContent, type ContentFileData, type ContentRegistry, type ContentTypeHandler, type DownloadedContent, type EngineScene, type FeatureDefinition, type FeatureEntry, type FeatureGroup, type FeaturePresenter, type FeatureRegistry, type FeatureScreenProps, type InferShape, type LoadingReporter, type LoadingTask, type LocalContentItem, type ModeEntry, type ModeRegistry, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type MultiplayerGame, type MultiplayerRegistry, type MultiplayerRoom, type MultiplayerScreenArgs, type MultiplayerSetting, type NotificationAction, type NotificationInput, type NotificationLevel, type PanelSlot, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
|
987
|
+
export { type AppNotifications, type CapturedContent, type ClipsRegistry, type ContentFileData, type ContentRegistry, type ContentTypeHandler, type DownloadedContent, type EngineScene, type FeatureDefinition, type FeatureEntry, type FeatureGroup, type FeaturePresenter, type FeatureRegistry, type FeatureScreenProps, type InferShape, type LoadingReporter, type LoadingTask, type LobbyHeroAction, type LobbyHeroDefinition, type LobbyHeroStage, type LobbyHeroView, type LobbyRegistry, type LocalContentItem, type ModeEntry, type ModeRegistry, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type MultiplayerGame, type MultiplayerRegistry, type MultiplayerRoom, type MultiplayerScreenArgs, type MultiplayerSetting, type NotificationAction, type NotificationInput, type NotificationLevel, type PanelSlot, type RecordingVoiceSource, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RealtimeSession, IDosGamesClient } from '@idosgames/core';
|
|
1
|
+
import { RealtimeSession, VoiceController, IDosGamesClient } from '@idosgames/core';
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
@@ -217,8 +217,12 @@ interface FeatureScreenProps {
|
|
|
217
217
|
interface FeatureDefinition {
|
|
218
218
|
/** Unique across the game — conventionally the owning module's id ("store"). */
|
|
219
219
|
id: string;
|
|
220
|
-
/**
|
|
221
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Menu label in the player's language. Prefer a function (`label: () => t("title")`): the lobby
|
|
222
|
+
* calls it on every render, so the tab follows the language switch (UI kit `setLanguage`) without
|
|
223
|
+
* a reload. A plain string stays as registered.
|
|
224
|
+
*/
|
|
225
|
+
label: string | (() => string);
|
|
222
226
|
/** Glyph name of @idosgames/react/ui ("shop", "hero"…), an emoji, or an image URL. */
|
|
223
227
|
icon?: string;
|
|
224
228
|
group?: FeatureGroup;
|
|
@@ -236,7 +240,9 @@ interface FeatureDefinition {
|
|
|
236
240
|
available?: boolean;
|
|
237
241
|
}
|
|
238
242
|
/** A registered feature plus its live badge and availability. */
|
|
239
|
-
interface FeatureEntry extends FeatureDefinition {
|
|
243
|
+
interface FeatureEntry extends Omit<FeatureDefinition, "label"> {
|
|
244
|
+
/** The label now — a function label is called on every read. */
|
|
245
|
+
readonly label: string;
|
|
240
246
|
badge: number;
|
|
241
247
|
/** The module that registered it. */
|
|
242
248
|
moduleId: string;
|
|
@@ -339,6 +345,13 @@ interface MultiplayerRoom {
|
|
|
339
345
|
readonly lateJoin: boolean;
|
|
340
346
|
/** Leave the room for good: closes the session and the rooms screen forgets it. */
|
|
341
347
|
leave(): Promise<void>;
|
|
348
|
+
/**
|
|
349
|
+
* The room's voice chat, or `null` when the room has none. The rooms system owns it and draws its
|
|
350
|
+
* controls (the mic button over the game included); a game reads it for its own touches — who is
|
|
351
|
+
* speaking (`isSpeaking`, `on("change")`), a talking player's marker, proximity volume
|
|
352
|
+
* (`setPeerVolume` by distance).
|
|
353
|
+
*/
|
|
354
|
+
readonly voice: VoiceController | null;
|
|
342
355
|
}
|
|
343
356
|
/** What `ctx.features.open("multiplayer", args)` understands. */
|
|
344
357
|
interface MultiplayerScreenArgs {
|
|
@@ -357,6 +370,84 @@ interface MultiplayerRegistry {
|
|
|
357
370
|
subscribe(listener: () => void): () => void;
|
|
358
371
|
}
|
|
359
372
|
|
|
373
|
+
/** A button under the showcase or on the "Play" card. */
|
|
374
|
+
interface LobbyHeroAction {
|
|
375
|
+
id: string;
|
|
376
|
+
/** Already in the player's language. */
|
|
377
|
+
label: string;
|
|
378
|
+
/** Glyph name of @idosgames/react/ui ("edit", "star"…), an emoji, or an image URL. */
|
|
379
|
+
icon?: string;
|
|
380
|
+
/** Pressed. Omit when the button only opens `menu`. */
|
|
381
|
+
run?(): void;
|
|
382
|
+
/** A short list shown when the button is pressed (an emote picker) — each entry runs on its own. */
|
|
383
|
+
menu?: LobbyHeroAction[];
|
|
384
|
+
disabled?: boolean;
|
|
385
|
+
}
|
|
386
|
+
/** What the lobby lays out around the showcase. Read through `view()`, redrawn on `subscribe`. */
|
|
387
|
+
interface LobbyHeroView {
|
|
388
|
+
/** Big name under the showcase (the character's name). */
|
|
389
|
+
title?: string;
|
|
390
|
+
/** A line under it. */
|
|
391
|
+
subtitle?: string;
|
|
392
|
+
/** Buttons under the name ("Edit", "Emote"). */
|
|
393
|
+
actions?: LobbyHeroAction[];
|
|
394
|
+
/**
|
|
395
|
+
* The card next to "Play" — WHAT will be played: a caption ("World"), a name ("Island"), buttons
|
|
396
|
+
* to change it ("Change", "New"). Omitted — "Play" alone.
|
|
397
|
+
*/
|
|
398
|
+
play?: {
|
|
399
|
+
caption?: string;
|
|
400
|
+
title?: string;
|
|
401
|
+
actions?: LobbyHeroAction[];
|
|
402
|
+
/** Overflow menu of the card ("⋯": download, upload…). */
|
|
403
|
+
more?: LobbyHeroAction[];
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/** Handed to `mount`: where on the host the main figure should stand. */
|
|
407
|
+
interface LobbyHeroStage {
|
|
408
|
+
/**
|
|
409
|
+
* The free spot between the lobby's interface where the figure should stand, in CSS pixels of the
|
|
410
|
+
* host element (it moves with the layout — read it every frame). `null` — not laid out yet.
|
|
411
|
+
*/
|
|
412
|
+
spot(): {
|
|
413
|
+
x: number;
|
|
414
|
+
y: number;
|
|
415
|
+
w: number;
|
|
416
|
+
h: number;
|
|
417
|
+
} | null;
|
|
418
|
+
}
|
|
419
|
+
interface LobbyHeroDefinition {
|
|
420
|
+
/** The game mode "Play" starts (a route id of the game). */
|
|
421
|
+
mode: string;
|
|
422
|
+
/**
|
|
423
|
+
* Put the showcase into `host` — an element filling the lobby's home behind its interface. Called
|
|
424
|
+
* when the home is shown, the returned function when it is left (stop the render loop there). The
|
|
425
|
+
* same hero may be mounted again later.
|
|
426
|
+
*/
|
|
427
|
+
mount(host: HTMLElement, stage: LobbyHeroStage): () => void;
|
|
428
|
+
/** The current texts and buttons. Cheap — called on every redraw. */
|
|
429
|
+
view(): LobbyHeroView;
|
|
430
|
+
/** Called by the hero whenever `view()` would answer differently. Returns the unsubscribe. */
|
|
431
|
+
subscribe(listener: () => void): () => void;
|
|
432
|
+
/**
|
|
433
|
+
* "Play" was pressed: return false to stay in the lobby (nothing to play yet). Default: switch to
|
|
434
|
+
* `mode`.
|
|
435
|
+
*/
|
|
436
|
+
beforePlay?(): boolean;
|
|
437
|
+
}
|
|
438
|
+
/** `ctx.lobby` — the app's lobby as seen by a module. */
|
|
439
|
+
interface LobbyRegistry {
|
|
440
|
+
/** Put this game's showcase on the lobby's home. Returns the unregister function. */
|
|
441
|
+
setHero(hero: LobbyHeroDefinition): () => void;
|
|
442
|
+
/** The hero on the home now (the lobby reads it). */
|
|
443
|
+
hero(): LobbyHeroDefinition | null;
|
|
444
|
+
subscribe(listener: () => void): () => void;
|
|
445
|
+
/** Whether the app has a lobby to return to (the base is installed). */
|
|
446
|
+
available(): boolean;
|
|
447
|
+
/** Go to the lobby — a game's "To the lobby" in its pause menu. False — there is no lobby. */
|
|
448
|
+
open(): boolean;
|
|
449
|
+
}
|
|
450
|
+
|
|
360
451
|
/** One piece of work on the loading screen. Call `done()` exactly once, also on failure. */
|
|
361
452
|
interface LoadingTask {
|
|
362
453
|
/**
|
|
@@ -441,6 +532,32 @@ interface AppNotifications {
|
|
|
441
532
|
dismiss(id: string): void;
|
|
442
533
|
}
|
|
443
534
|
|
|
535
|
+
/** The room's voices, as the recording takes them (the multiplayer system provides this). */
|
|
536
|
+
interface RecordingVoiceSource {
|
|
537
|
+
/** This player's microphone track (silent while not transmitting); null — mic off. */
|
|
538
|
+
mic(): MediaStreamTrack | null;
|
|
539
|
+
/** Other players' voice tracks. */
|
|
540
|
+
others(): MediaStreamTrack[];
|
|
541
|
+
/** The publisher allows other players' voices in videos (`Multiplayer.RecordVoices`). */
|
|
542
|
+
recordOthers(): boolean;
|
|
543
|
+
/** Called when tracks come or go (a player joined, the mic went on). */
|
|
544
|
+
subscribe(listener: () => void): () => void;
|
|
545
|
+
}
|
|
546
|
+
/** `ctx.clips`. */
|
|
547
|
+
interface ClipsRegistry {
|
|
548
|
+
/**
|
|
549
|
+
* A bright moment right now. `weight` 1–10 (default 5): how much it is worth showing — a boss
|
|
550
|
+
* kill 8, a level-up 4. Close marks merge into one clip (their weights add up).
|
|
551
|
+
*/
|
|
552
|
+
mark(name: string, weight?: number): void;
|
|
553
|
+
/** The multiplayer system registers the room's voices here; `null` when the room is gone. */
|
|
554
|
+
setVoiceSource(source: RecordingVoiceSource | null): void;
|
|
555
|
+
/** This player is making a long recording right now. */
|
|
556
|
+
isRecording(): boolean;
|
|
557
|
+
/** Called when `isRecording` changes. */
|
|
558
|
+
subscribe(listener: () => void): () => void;
|
|
559
|
+
}
|
|
560
|
+
|
|
444
561
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
445
562
|
type ModuleType = "game" | "app" | "ai-app";
|
|
446
563
|
/**
|
|
@@ -629,6 +746,12 @@ interface ModuleContext {
|
|
|
629
746
|
* builds its "Play" buttons from it; a game can tell whether a lobby exists to return to.
|
|
630
747
|
*/
|
|
631
748
|
modes: ModeRegistry;
|
|
749
|
+
/**
|
|
750
|
+
* The app's lobby: a game puts its showcase on the lobby's home (`setHero` — its character on a
|
|
751
|
+
* pedestal, "Play" next to it) and sends the player back to it from its pause menu (`open`). See
|
|
752
|
+
* `LobbyHeroDefinition`.
|
|
753
|
+
*/
|
|
754
|
+
lobby: LobbyRegistry;
|
|
632
755
|
/**
|
|
633
756
|
* Switch the host to another mode (a route id registered by any module) — e.g. the lobby starts a
|
|
634
757
|
* game, the Workshop opens a world and hands the screen to the game. Unknown ids are ignored. Called
|
|
@@ -660,6 +783,12 @@ interface ModuleContext {
|
|
|
660
783
|
* one look, one queue, one sound for the whole app. The module's own id is the default `source`.
|
|
661
784
|
*/
|
|
662
785
|
notifications: AppNotifications;
|
|
786
|
+
/**
|
|
787
|
+
* The platform's video recording (see `ClipsRegistry`): a game marks bright moments
|
|
788
|
+
* (`ctx.clips.mark("boss_down", 8)`) — they become the player's "Best moments" clips on
|
|
789
|
+
* idosgames.com. A no-op anywhere else.
|
|
790
|
+
*/
|
|
791
|
+
clips: ClipsRegistry;
|
|
663
792
|
}
|
|
664
793
|
/**
|
|
665
794
|
* A module's debug surface for the agent: read its state, drive it with named actions.
|
|
@@ -752,6 +881,21 @@ interface EngineScene {
|
|
|
752
881
|
* host falls back to reading the canvas itself, which is correct for 2D canvases.
|
|
753
882
|
*/
|
|
754
883
|
capture?(): Promise<Blob | null>;
|
|
884
|
+
/**
|
|
885
|
+
* Where the action is right now, 0–1 on both axes of the canvas (the player, the explosion) — for
|
|
886
|
+
* the platform's video recording: a vertical (TikTok) cut of a landscape game follows this point
|
|
887
|
+
* instead of cutting the middle. Called a few times a second; keep it cheap. Omit — the centre.
|
|
888
|
+
*/
|
|
889
|
+
focus?(): {
|
|
890
|
+
x: number;
|
|
891
|
+
y: number;
|
|
892
|
+
} | null;
|
|
893
|
+
/**
|
|
894
|
+
* The game's own sound as a MediaStream, for the platform's video recording — e.g. a
|
|
895
|
+
* `MediaStreamAudioDestinationNode` your audio graph also feeds (Three.js: `listener.context`).
|
|
896
|
+
* Omit — videos carry the voices but not the game's sound.
|
|
897
|
+
*/
|
|
898
|
+
audio?(): MediaStream | null;
|
|
755
899
|
/** Permanent teardown — dispose GPU resources, remove listeners. */
|
|
756
900
|
destroy(): void;
|
|
757
901
|
}
|
|
@@ -790,9 +934,15 @@ interface RouteEntry {
|
|
|
790
934
|
* A game that lives INSIDE the lobby: the base keeps the lobby's balances on top and its tabs at
|
|
791
935
|
* the bottom around it, "Play" is this game, and the app starts in it after sign-in (idle-rpg).
|
|
792
936
|
* Without it (the default) a game takes the whole screen, launched by "Play" in the lobby, with a
|
|
793
|
-
* small button back (
|
|
937
|
+
* small button back (board-game).
|
|
794
938
|
*/
|
|
795
939
|
inLobby?: boolean;
|
|
940
|
+
/**
|
|
941
|
+
* A full-screen game with its OWN way back to the lobby (a pause menu with "To the lobby" that
|
|
942
|
+
* calls `ctx.lobby.open()` — voxelcraft): the base does not draw its round button back over it.
|
|
943
|
+
* The game must then offer that way on every device (a pause button on touch screens).
|
|
944
|
+
*/
|
|
945
|
+
ownExit?: boolean;
|
|
796
946
|
}
|
|
797
947
|
/** A mode of the app as every module sees it (`ctx.modes`): a registered route plus its owner. */
|
|
798
948
|
interface ModeEntry {
|
|
@@ -807,6 +957,8 @@ interface ModeEntry {
|
|
|
807
957
|
moduleType: ModuleType;
|
|
808
958
|
/** The route's `inLobby`: the game sits inside the lobby rather than taking the whole screen. */
|
|
809
959
|
inLobby?: boolean;
|
|
960
|
+
/** The route's `ownExit`: the game leads back to the lobby itself — no button back over it. */
|
|
961
|
+
ownExit?: boolean;
|
|
810
962
|
}
|
|
811
963
|
/** `ctx.modes` — the modes of the app, live. Switch with `ctx.navigate(id)`. */
|
|
812
964
|
interface ModeRegistry {
|
|
@@ -832,4 +984,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
832
984
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
833
985
|
declare function defineModule(module: Module): Module;
|
|
834
986
|
|
|
835
|
-
export { type AppNotifications, type CapturedContent, type ContentFileData, type ContentRegistry, type ContentTypeHandler, type DownloadedContent, type EngineScene, type FeatureDefinition, type FeatureEntry, type FeatureGroup, type FeaturePresenter, type FeatureRegistry, type FeatureScreenProps, type InferShape, type LoadingReporter, type LoadingTask, type LocalContentItem, type ModeEntry, type ModeRegistry, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type MultiplayerGame, type MultiplayerRegistry, type MultiplayerRoom, type MultiplayerScreenArgs, type MultiplayerSetting, type NotificationAction, type NotificationInput, type NotificationLevel, type PanelSlot, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
|
987
|
+
export { type AppNotifications, type CapturedContent, type ClipsRegistry, type ContentFileData, type ContentRegistry, type ContentTypeHandler, type DownloadedContent, type EngineScene, type FeatureDefinition, type FeatureEntry, type FeatureGroup, type FeaturePresenter, type FeatureRegistry, type FeatureScreenProps, type InferShape, type LoadingReporter, type LoadingTask, type LobbyHeroAction, type LobbyHeroDefinition, type LobbyHeroStage, type LobbyHeroView, type LobbyRegistry, type LocalContentItem, type ModeEntry, type ModeRegistry, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type MultiplayerGame, type MultiplayerRegistry, type MultiplayerRoom, type MultiplayerScreenArgs, type MultiplayerSetting, type NotificationAction, type NotificationInput, type NotificationLevel, type PanelSlot, type RecordingVoiceSource, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var x=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function S(e){return typeof e=="string"&&x.test(e)}function
|
|
1
|
+
var x=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function S(e){return typeof e=="string"&&x.test(e)}function M(e){let t=e.indexOf(":");return t<0?"":e.slice(0,t)}var c=/^(string|number|boolean)(\[\])?(\?)?$/;function u(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function l(e,t){return e==="number"?typeof t=="number"&&Number.isFinite(t):typeof t===e}function f(e,t,r){if(!u(t))return `${r||"payload"} must be an object`;for(let[n,o]of Object.entries(e)){let i=r?`${r}.${n}`:n,a=t[n];if(typeof o!="string"){let p=f(o,a,i);if(p)return p;continue}let d=c.exec(o);if(!d)return `${i}: invalid descriptor "${o}"`;let[,s="",g,m]=d;if(a===void 0){if(m)continue;return `${i} is missing`}if(g){if(!Array.isArray(a))return `${i} must be ${s}[]`;if(!a.every(p=>l(s,p)))return `${i} must contain only ${s} values`;continue}if(!l(s,a))return `${i} must be a ${s}`}return null}function y(e,t){for(let[r,n]of Object.entries(e)){let o=t?`${t}.${r}`:r;if(typeof n=="string"){if(!c.test(n))throw new Error(`shape(): field "${o}" has invalid type "${n}" \u2014 use "string" | "number" | "boolean", optionally with "[]" and/or a trailing "?"`)}else if(u(n))y(n,o);else throw new Error(`shape(): field "${o}" must be a type string or an object`)}}function h(e){return y(e,""),{descriptor:e,check:t=>f(e,t,"")}}function v(e,t){if(!S(e))throw new Error(`defineTopic(): "${e}" is not a valid topic name \u2014 expected "<module-id>:<event>@<major>", e.g. "idle-rpg:character-upgraded@1"`);return {name:e,shape:t}}var T={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var b=["currency-bar","wallet","status","account","menu","modes"];function R(e){return typeof e=="string"&&b.includes(e)}function D(e){return e}export{b as SHARED_UI_ROLES,x as TOPIC_NAME_RE,D as defineModule,v as defineTopic,T as hostTopics,R as isSharedUiRole,S as isTopicName,h as shape,M as topicNamespace};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idosgames/module-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "The composable-module contract for the iDosGames host shell: Module / ModuleContext types plus tiny authoring helpers. Framework-neutral at runtime — a module can be a game (Three/Phaser), a plain app, or an AI app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"//": "@idosgames/core is a real range, not '*': a published contract must name the version of core whose IDosGamesClient/SdkEvents shapes it references. Bump in step with core (see RELEASING.md). react is an OPTIONAL peer — the contract references React's ComponentType type only (a UI panel is a React component), which is erased at build, so a runtime-only module needs no React.",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@idosgames/core": "^0.
|
|
49
|
+
"@idosgames/core": "^0.22.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^18.3.1 || ^19.0.0"
|