@idosgames/module-sdk 0.6.0 → 0.8.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 +207 -5
- package/dist/index.d.ts +207 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
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 T(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,
|
|
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 T(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 b={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var M=["currency-bar","wallet","status","account","menu","modes"];function R(e){return typeof e=="string"&&M.includes(e)}function D(e){return e}exports.SHARED_UI_ROLES=M;exports.TOPIC_NAME_RE=x;exports.defineModule=D;exports.defineTopic=v;exports.hostTopics=b;exports.isSharedUiRole=R;exports.isTopicName=S;exports.shape=h;exports.topicNamespace=T;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IDosGamesClient } from '@idosgames/core';
|
|
1
|
+
import { RealtimeSession, IDosGamesClient } from '@idosgames/core';
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
@@ -276,6 +276,171 @@ interface FeatureRegistry {
|
|
|
276
276
|
setPresenter(presenter: FeaturePresenter): () => void;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/** A room setting the host picks when creating a room for the game (guest mode, cheats…). */
|
|
280
|
+
interface MultiplayerSetting {
|
|
281
|
+
/** Key in `MultiplayerRoom.settings`. */
|
|
282
|
+
id: string;
|
|
283
|
+
/** Label, already in the player's language. */
|
|
284
|
+
label: string;
|
|
285
|
+
/** A choice of options, or an on/off switch. */
|
|
286
|
+
kind: "choice" | "toggle";
|
|
287
|
+
/** For `choice`: value + label of every option. */
|
|
288
|
+
options?: Array<{
|
|
289
|
+
value: string;
|
|
290
|
+
label: string;
|
|
291
|
+
}>;
|
|
292
|
+
/** Value when the host does not change it. */
|
|
293
|
+
default: string | boolean;
|
|
294
|
+
}
|
|
295
|
+
/** What a game declares about playing online (`ctx.multiplayer.registerGame`). */
|
|
296
|
+
interface MultiplayerGame {
|
|
297
|
+
/** The game's mode (its route id) — the room's game and where "Play" takes the room. */
|
|
298
|
+
mode: string;
|
|
299
|
+
/** Label of the game in the rooms screen; defaults to the route's label. */
|
|
300
|
+
label?: string;
|
|
301
|
+
/**
|
|
302
|
+
* Who connects to whom: `"Host"` (star — the host links to everyone, the others only to the host)
|
|
303
|
+
* or `"Mesh"` (everyone to everyone). Default `"Mesh"`.
|
|
304
|
+
*/
|
|
305
|
+
topology?: "Host" | "Mesh";
|
|
306
|
+
/**
|
|
307
|
+
* `"P2P"` — the game has its own chat over the DataChannels, the lobby draws none;
|
|
308
|
+
* `"ServerRelay"` (default) — the lobby's room chat goes through the server.
|
|
309
|
+
*/
|
|
310
|
+
chatMode?: "P2P" | "ServerRelay";
|
|
311
|
+
/** Most players in one room (the title's limit caps it). Default 8. */
|
|
312
|
+
maxPlayers?: number;
|
|
313
|
+
/**
|
|
314
|
+
* A player may join a room whose game is already running — the rooms screen lets them in and
|
|
315
|
+
* `start` gets called for them right away (the game's own "late joiner" path). Default `false`.
|
|
316
|
+
*/
|
|
317
|
+
joinInProgress?: boolean;
|
|
318
|
+
/** Room settings the host picks on creation; the game reads them in `room.settings`. */
|
|
319
|
+
settings?: MultiplayerSetting[];
|
|
320
|
+
/**
|
|
321
|
+
* The room goes into this game: switch the game to network play over `room.session`. Called on
|
|
322
|
+
* EVERY member's device (the host's first) — after the app switched to the game's mode.
|
|
323
|
+
*/
|
|
324
|
+
start(room: MultiplayerRoom): void | Promise<void>;
|
|
325
|
+
}
|
|
326
|
+
/** The room handed to a game. */
|
|
327
|
+
interface MultiplayerRoom {
|
|
328
|
+
/** The live session — links, messages, members, host changes. Opened by the rooms system. */
|
|
329
|
+
readonly session: RealtimeSession;
|
|
330
|
+
/** The game's mode. */
|
|
331
|
+
readonly mode: string;
|
|
332
|
+
/** Whether this player created the game (the host at start). Live host: `session.hostId`. */
|
|
333
|
+
readonly isHost: boolean;
|
|
334
|
+
/** The room's settings as the host picked them (defaults filled in). */
|
|
335
|
+
readonly settings: Readonly<Record<string, string | boolean>>;
|
|
336
|
+
/**
|
|
337
|
+
* The game joins a running room (`joinInProgress`) rather than starting together with it.
|
|
338
|
+
*/
|
|
339
|
+
readonly lateJoin: boolean;
|
|
340
|
+
/** Leave the room for good: closes the session and the rooms screen forgets it. */
|
|
341
|
+
leave(): Promise<void>;
|
|
342
|
+
}
|
|
343
|
+
/** What `ctx.features.open("multiplayer", args)` understands. */
|
|
344
|
+
interface MultiplayerScreenArgs {
|
|
345
|
+
/** Show only this game's rooms and preselect it in "Create a room". */
|
|
346
|
+
mode?: string;
|
|
347
|
+
/** Open straight on "Create a room" (a game's "Open for friends"). */
|
|
348
|
+
create?: boolean;
|
|
349
|
+
}
|
|
350
|
+
/** `ctx.multiplayer` — the host's registry of games that play online, shared by every module. */
|
|
351
|
+
interface MultiplayerRegistry {
|
|
352
|
+
/** A game declares that it plays online. Returns an unregister function. */
|
|
353
|
+
registerGame(game: MultiplayerGame): () => void;
|
|
354
|
+
getGame(mode: string): MultiplayerGame | undefined;
|
|
355
|
+
listGames(): MultiplayerGame[];
|
|
356
|
+
/** Called when a game is registered or removed. */
|
|
357
|
+
subscribe(listener: () => void): () => void;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** One piece of work on the loading screen. Call `done()` exactly once, also on failure. */
|
|
361
|
+
interface LoadingTask {
|
|
362
|
+
/**
|
|
363
|
+
* Progress 0..1 when it is known (a bar appears), `null` when it is not (just the spinner). An
|
|
364
|
+
* optional new label replaces the current one ("Building terrain…", "Loading textures…").
|
|
365
|
+
*/
|
|
366
|
+
progress(value: number | null, label?: string): void;
|
|
367
|
+
done(): void;
|
|
368
|
+
}
|
|
369
|
+
interface LoadingReporter {
|
|
370
|
+
/**
|
|
371
|
+
* Show the loading screen until `done()`. Quick work never flashes it: the screen appears only if
|
|
372
|
+
* the work is still running a moment later. `label` — what is loading, in the player's language.
|
|
373
|
+
*/
|
|
374
|
+
begin(label?: string): LoadingTask;
|
|
375
|
+
/** The loading screen while `work` runs; resolves or rejects with it. */
|
|
376
|
+
track<T>(work: Promise<T>, label?: string): Promise<T>;
|
|
377
|
+
/**
|
|
378
|
+
* For work that BLOCKS the page (synchronous generation, a big parse): shows the screen at once,
|
|
379
|
+
* waits until it is painted, then calls `work`. Calling heavy synchronous code straight after
|
|
380
|
+
* `begin()` would freeze the page before the screen ever appears.
|
|
381
|
+
*/
|
|
382
|
+
run<T>(work: () => T | Promise<T>, label?: string): Promise<T>;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* How important it is — decides the colour, the icon, the sound and how long a toast stays:
|
|
387
|
+
* info — neutral news; success — something the player did worked;
|
|
388
|
+
* warning — needs attention, nothing is lost yet; error — something failed (say what to do);
|
|
389
|
+
* reward — the player received something.
|
|
390
|
+
*/
|
|
391
|
+
type NotificationLevel = "info" | "success" | "warning" | "error" | "reward";
|
|
392
|
+
interface NotificationAction {
|
|
393
|
+
/** What `alert()` resolves with when this button is pressed. Defaults to the label. */
|
|
394
|
+
id?: string;
|
|
395
|
+
label: string;
|
|
396
|
+
/** The main action — drawn as the coloured button. At most one. */
|
|
397
|
+
primary?: boolean;
|
|
398
|
+
/** Open this screen (a feature id, e.g. "quests") on press. */
|
|
399
|
+
openFeature?: string;
|
|
400
|
+
/** Called on press, before the notification closes. */
|
|
401
|
+
onPress?: () => void;
|
|
402
|
+
}
|
|
403
|
+
interface NotificationInput {
|
|
404
|
+
level?: NotificationLevel;
|
|
405
|
+
/** A short headline. Optional for toasts, recommended for alerts. */
|
|
406
|
+
title?: string;
|
|
407
|
+
/** The text, already in the player's language. */
|
|
408
|
+
message?: string;
|
|
409
|
+
/**
|
|
410
|
+
* An icon instead of the level's own: a UI-kit glyph name ("trophy", "gift", "chest"…), an emoji
|
|
411
|
+
* or an image URL.
|
|
412
|
+
*/
|
|
413
|
+
icon?: string;
|
|
414
|
+
/** A picture: a thumbnail on a toast, a large image on an alert (an item, a banner, an avatar). */
|
|
415
|
+
image?: string;
|
|
416
|
+
/**
|
|
417
|
+
* The system it belongs to — a feature id ("quests", "store"). Shows that screen's name and icon on
|
|
418
|
+
* the notification, and a tap on the toast opens it. `ctx.notifications` fills in the calling
|
|
419
|
+
* module's own id; pass `null` for none.
|
|
420
|
+
*/
|
|
421
|
+
source?: string | null;
|
|
422
|
+
/**
|
|
423
|
+
* The same key replaces a notification that is still shown instead of adding one more ("Saving…"
|
|
424
|
+
* → "Saved", the same error again). Identical toasts without a key are merged into one "×2".
|
|
425
|
+
*/
|
|
426
|
+
key?: string;
|
|
427
|
+
/** Toast only: how long it stays, ms. `0` — until the player closes it. Default by level. */
|
|
428
|
+
duration?: number;
|
|
429
|
+
/** Buttons. A toast shows the first one; an alert all of them (default: a single "OK"). */
|
|
430
|
+
actions?: NotificationAction[];
|
|
431
|
+
}
|
|
432
|
+
interface AppNotifications {
|
|
433
|
+
/** A toast. A bare string is an info toast. Returns its id (for `dismiss`). */
|
|
434
|
+
toast(notification: NotificationInput | string): string;
|
|
435
|
+
/**
|
|
436
|
+
* An alert window, queued behind any other. Resolves with the pressed action's id, or `null` when
|
|
437
|
+
* the player closed it without choosing.
|
|
438
|
+
*/
|
|
439
|
+
alert(notification: NotificationInput): Promise<string | null>;
|
|
440
|
+
/** Take a toast or a waiting alert away (an alert resolves with `null`). */
|
|
441
|
+
dismiss(id: string): void;
|
|
442
|
+
}
|
|
443
|
+
|
|
279
444
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
280
445
|
type ModuleType = "game" | "app" | "ai-app";
|
|
281
446
|
/**
|
|
@@ -328,10 +493,19 @@ interface ModuleManifest {
|
|
|
328
493
|
*/
|
|
329
494
|
type?: "template" | "feature";
|
|
330
495
|
/**
|
|
331
|
-
* Part of the
|
|
332
|
-
*
|
|
496
|
+
* Part of the base: the platform installs this system into EVERY new project together with the base
|
|
497
|
+
* (sign-in + the lobby) — only profile, inventory, invite, friends, quests and mailbox (owner's rule
|
|
498
|
+
* 29.09.2026). Every other module is installed only when it is needed: a game brings the ones it
|
|
499
|
+
* `uses`, the AI Coder installs any other one as soon as the task needs what it `provides`. A creator removes it like any module.
|
|
333
500
|
*/
|
|
334
501
|
inBase?: boolean;
|
|
502
|
+
/**
|
|
503
|
+
* A game's modules: catalog ids of the systems this game plugs into (`ctx.multiplayer` →
|
|
504
|
+
* `"multiplayer"`, `ctx.content` → `"workshop"`, …). The platform installs them together with the game
|
|
505
|
+
* — a new title on this game gets them, and so does a project the AI Coder adds the game to.
|
|
506
|
+
* `npm run check` makes a module that registers in `ctx.multiplayer` / `ctx.content` list them.
|
|
507
|
+
*/
|
|
508
|
+
uses?: string[];
|
|
335
509
|
/** Overrides the catalog display name; defaults to `ModuleMeta.name` from `module.ts`. */
|
|
336
510
|
name?: string;
|
|
337
511
|
/** One-line pitch shown on the catalog card. */
|
|
@@ -431,6 +605,12 @@ interface ModuleContext {
|
|
|
431
605
|
* rather than a mode of its own; `open(id)` opens another module's screen. See `FeatureDefinition`.
|
|
432
606
|
*/
|
|
433
607
|
features: FeatureRegistry;
|
|
608
|
+
/**
|
|
609
|
+
* Games that play online and the rooms system that runs them (see `MultiplayerGame`). A game
|
|
610
|
+
* registers here (`registerGame`) and takes the room in `start`; it never creates or joins rooms
|
|
611
|
+
* itself. Without the multiplayer system installed nothing ever calls `start`.
|
|
612
|
+
*/
|
|
613
|
+
multiplayer: MultiplayerRegistry;
|
|
434
614
|
/**
|
|
435
615
|
* The app's modes — every route any module registered, and which one is on screen. The lobby
|
|
436
616
|
* builds its "Play" buttons from it; a game can tell whether a lobby exists to return to.
|
|
@@ -455,6 +635,18 @@ interface ModuleContext {
|
|
|
455
635
|
* @see ModuleAgentApi
|
|
456
636
|
*/
|
|
457
637
|
exposeToAgent(api: ModuleAgentApi): void;
|
|
638
|
+
/**
|
|
639
|
+
* The app's loading screen (see `LoadingReporter`): `ctx.loading.track(work, "Building the world…")`
|
|
640
|
+
* for any long work of the module, so the player never faces a still screen wondering whether the
|
|
641
|
+
* app froze. The host shows it by itself while a scene starts — this is for the module's own work.
|
|
642
|
+
*/
|
|
643
|
+
loading: LoadingReporter;
|
|
644
|
+
/**
|
|
645
|
+
* The app's notifications (see `AppNotifications`): `ctx.notifications.toast({ level: "success",
|
|
646
|
+
* message })` for news, `alert({ … })` for what the player must answer. Never draw your own banner:
|
|
647
|
+
* one look, one queue, one sound for the whole app. The module's own id is the default `source`.
|
|
648
|
+
*/
|
|
649
|
+
notifications: AppNotifications;
|
|
458
650
|
}
|
|
459
651
|
/**
|
|
460
652
|
* A module's debug surface for the agent: read its state, drive it with named actions.
|
|
@@ -509,8 +701,18 @@ interface SceneMountContext {
|
|
|
509
701
|
interface EngineScene {
|
|
510
702
|
/** Preferred host surface. Defaults to `"fullbleed-canvas"` when omitted. */
|
|
511
703
|
readonly surface?: SurfaceKind;
|
|
512
|
-
/**
|
|
704
|
+
/**
|
|
705
|
+
* Create the renderer and attach to the host element. Called once. The host shows its loading
|
|
706
|
+
* screen BEFORE calling it and lets it paint, so heavy synchronous work here (generating terrain,
|
|
707
|
+
* building meshes) never looks like a frozen app.
|
|
708
|
+
*/
|
|
513
709
|
mount(ctx: SceneMountContext): void;
|
|
710
|
+
/**
|
|
711
|
+
* Resolves when the scene is really playable (the world is built, the save is loaded). The host
|
|
712
|
+
* keeps its loading screen up until then. Omit it when the first frame after `mount` is already
|
|
713
|
+
* the game — the host then hides the screen after that frame.
|
|
714
|
+
*/
|
|
715
|
+
ready?(): Promise<void>;
|
|
514
716
|
/** This scene became the active mode — resume RAF / re-acquire input. */
|
|
515
717
|
activate?(): void;
|
|
516
718
|
/** This scene was hidden — pause RAF / release input. Not destroyed; may re-activate. */
|
|
@@ -600,4 +802,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
600
802
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
601
803
|
declare function defineModule(module: Module): Module;
|
|
602
804
|
|
|
603
|
-
export { 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 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 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 };
|
|
805
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IDosGamesClient } from '@idosgames/core';
|
|
1
|
+
import { RealtimeSession, IDosGamesClient } from '@idosgames/core';
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
@@ -276,6 +276,171 @@ interface FeatureRegistry {
|
|
|
276
276
|
setPresenter(presenter: FeaturePresenter): () => void;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/** A room setting the host picks when creating a room for the game (guest mode, cheats…). */
|
|
280
|
+
interface MultiplayerSetting {
|
|
281
|
+
/** Key in `MultiplayerRoom.settings`. */
|
|
282
|
+
id: string;
|
|
283
|
+
/** Label, already in the player's language. */
|
|
284
|
+
label: string;
|
|
285
|
+
/** A choice of options, or an on/off switch. */
|
|
286
|
+
kind: "choice" | "toggle";
|
|
287
|
+
/** For `choice`: value + label of every option. */
|
|
288
|
+
options?: Array<{
|
|
289
|
+
value: string;
|
|
290
|
+
label: string;
|
|
291
|
+
}>;
|
|
292
|
+
/** Value when the host does not change it. */
|
|
293
|
+
default: string | boolean;
|
|
294
|
+
}
|
|
295
|
+
/** What a game declares about playing online (`ctx.multiplayer.registerGame`). */
|
|
296
|
+
interface MultiplayerGame {
|
|
297
|
+
/** The game's mode (its route id) — the room's game and where "Play" takes the room. */
|
|
298
|
+
mode: string;
|
|
299
|
+
/** Label of the game in the rooms screen; defaults to the route's label. */
|
|
300
|
+
label?: string;
|
|
301
|
+
/**
|
|
302
|
+
* Who connects to whom: `"Host"` (star — the host links to everyone, the others only to the host)
|
|
303
|
+
* or `"Mesh"` (everyone to everyone). Default `"Mesh"`.
|
|
304
|
+
*/
|
|
305
|
+
topology?: "Host" | "Mesh";
|
|
306
|
+
/**
|
|
307
|
+
* `"P2P"` — the game has its own chat over the DataChannels, the lobby draws none;
|
|
308
|
+
* `"ServerRelay"` (default) — the lobby's room chat goes through the server.
|
|
309
|
+
*/
|
|
310
|
+
chatMode?: "P2P" | "ServerRelay";
|
|
311
|
+
/** Most players in one room (the title's limit caps it). Default 8. */
|
|
312
|
+
maxPlayers?: number;
|
|
313
|
+
/**
|
|
314
|
+
* A player may join a room whose game is already running — the rooms screen lets them in and
|
|
315
|
+
* `start` gets called for them right away (the game's own "late joiner" path). Default `false`.
|
|
316
|
+
*/
|
|
317
|
+
joinInProgress?: boolean;
|
|
318
|
+
/** Room settings the host picks on creation; the game reads them in `room.settings`. */
|
|
319
|
+
settings?: MultiplayerSetting[];
|
|
320
|
+
/**
|
|
321
|
+
* The room goes into this game: switch the game to network play over `room.session`. Called on
|
|
322
|
+
* EVERY member's device (the host's first) — after the app switched to the game's mode.
|
|
323
|
+
*/
|
|
324
|
+
start(room: MultiplayerRoom): void | Promise<void>;
|
|
325
|
+
}
|
|
326
|
+
/** The room handed to a game. */
|
|
327
|
+
interface MultiplayerRoom {
|
|
328
|
+
/** The live session — links, messages, members, host changes. Opened by the rooms system. */
|
|
329
|
+
readonly session: RealtimeSession;
|
|
330
|
+
/** The game's mode. */
|
|
331
|
+
readonly mode: string;
|
|
332
|
+
/** Whether this player created the game (the host at start). Live host: `session.hostId`. */
|
|
333
|
+
readonly isHost: boolean;
|
|
334
|
+
/** The room's settings as the host picked them (defaults filled in). */
|
|
335
|
+
readonly settings: Readonly<Record<string, string | boolean>>;
|
|
336
|
+
/**
|
|
337
|
+
* The game joins a running room (`joinInProgress`) rather than starting together with it.
|
|
338
|
+
*/
|
|
339
|
+
readonly lateJoin: boolean;
|
|
340
|
+
/** Leave the room for good: closes the session and the rooms screen forgets it. */
|
|
341
|
+
leave(): Promise<void>;
|
|
342
|
+
}
|
|
343
|
+
/** What `ctx.features.open("multiplayer", args)` understands. */
|
|
344
|
+
interface MultiplayerScreenArgs {
|
|
345
|
+
/** Show only this game's rooms and preselect it in "Create a room". */
|
|
346
|
+
mode?: string;
|
|
347
|
+
/** Open straight on "Create a room" (a game's "Open for friends"). */
|
|
348
|
+
create?: boolean;
|
|
349
|
+
}
|
|
350
|
+
/** `ctx.multiplayer` — the host's registry of games that play online, shared by every module. */
|
|
351
|
+
interface MultiplayerRegistry {
|
|
352
|
+
/** A game declares that it plays online. Returns an unregister function. */
|
|
353
|
+
registerGame(game: MultiplayerGame): () => void;
|
|
354
|
+
getGame(mode: string): MultiplayerGame | undefined;
|
|
355
|
+
listGames(): MultiplayerGame[];
|
|
356
|
+
/** Called when a game is registered or removed. */
|
|
357
|
+
subscribe(listener: () => void): () => void;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** One piece of work on the loading screen. Call `done()` exactly once, also on failure. */
|
|
361
|
+
interface LoadingTask {
|
|
362
|
+
/**
|
|
363
|
+
* Progress 0..1 when it is known (a bar appears), `null` when it is not (just the spinner). An
|
|
364
|
+
* optional new label replaces the current one ("Building terrain…", "Loading textures…").
|
|
365
|
+
*/
|
|
366
|
+
progress(value: number | null, label?: string): void;
|
|
367
|
+
done(): void;
|
|
368
|
+
}
|
|
369
|
+
interface LoadingReporter {
|
|
370
|
+
/**
|
|
371
|
+
* Show the loading screen until `done()`. Quick work never flashes it: the screen appears only if
|
|
372
|
+
* the work is still running a moment later. `label` — what is loading, in the player's language.
|
|
373
|
+
*/
|
|
374
|
+
begin(label?: string): LoadingTask;
|
|
375
|
+
/** The loading screen while `work` runs; resolves or rejects with it. */
|
|
376
|
+
track<T>(work: Promise<T>, label?: string): Promise<T>;
|
|
377
|
+
/**
|
|
378
|
+
* For work that BLOCKS the page (synchronous generation, a big parse): shows the screen at once,
|
|
379
|
+
* waits until it is painted, then calls `work`. Calling heavy synchronous code straight after
|
|
380
|
+
* `begin()` would freeze the page before the screen ever appears.
|
|
381
|
+
*/
|
|
382
|
+
run<T>(work: () => T | Promise<T>, label?: string): Promise<T>;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* How important it is — decides the colour, the icon, the sound and how long a toast stays:
|
|
387
|
+
* info — neutral news; success — something the player did worked;
|
|
388
|
+
* warning — needs attention, nothing is lost yet; error — something failed (say what to do);
|
|
389
|
+
* reward — the player received something.
|
|
390
|
+
*/
|
|
391
|
+
type NotificationLevel = "info" | "success" | "warning" | "error" | "reward";
|
|
392
|
+
interface NotificationAction {
|
|
393
|
+
/** What `alert()` resolves with when this button is pressed. Defaults to the label. */
|
|
394
|
+
id?: string;
|
|
395
|
+
label: string;
|
|
396
|
+
/** The main action — drawn as the coloured button. At most one. */
|
|
397
|
+
primary?: boolean;
|
|
398
|
+
/** Open this screen (a feature id, e.g. "quests") on press. */
|
|
399
|
+
openFeature?: string;
|
|
400
|
+
/** Called on press, before the notification closes. */
|
|
401
|
+
onPress?: () => void;
|
|
402
|
+
}
|
|
403
|
+
interface NotificationInput {
|
|
404
|
+
level?: NotificationLevel;
|
|
405
|
+
/** A short headline. Optional for toasts, recommended for alerts. */
|
|
406
|
+
title?: string;
|
|
407
|
+
/** The text, already in the player's language. */
|
|
408
|
+
message?: string;
|
|
409
|
+
/**
|
|
410
|
+
* An icon instead of the level's own: a UI-kit glyph name ("trophy", "gift", "chest"…), an emoji
|
|
411
|
+
* or an image URL.
|
|
412
|
+
*/
|
|
413
|
+
icon?: string;
|
|
414
|
+
/** A picture: a thumbnail on a toast, a large image on an alert (an item, a banner, an avatar). */
|
|
415
|
+
image?: string;
|
|
416
|
+
/**
|
|
417
|
+
* The system it belongs to — a feature id ("quests", "store"). Shows that screen's name and icon on
|
|
418
|
+
* the notification, and a tap on the toast opens it. `ctx.notifications` fills in the calling
|
|
419
|
+
* module's own id; pass `null` for none.
|
|
420
|
+
*/
|
|
421
|
+
source?: string | null;
|
|
422
|
+
/**
|
|
423
|
+
* The same key replaces a notification that is still shown instead of adding one more ("Saving…"
|
|
424
|
+
* → "Saved", the same error again). Identical toasts without a key are merged into one "×2".
|
|
425
|
+
*/
|
|
426
|
+
key?: string;
|
|
427
|
+
/** Toast only: how long it stays, ms. `0` — until the player closes it. Default by level. */
|
|
428
|
+
duration?: number;
|
|
429
|
+
/** Buttons. A toast shows the first one; an alert all of them (default: a single "OK"). */
|
|
430
|
+
actions?: NotificationAction[];
|
|
431
|
+
}
|
|
432
|
+
interface AppNotifications {
|
|
433
|
+
/** A toast. A bare string is an info toast. Returns its id (for `dismiss`). */
|
|
434
|
+
toast(notification: NotificationInput | string): string;
|
|
435
|
+
/**
|
|
436
|
+
* An alert window, queued behind any other. Resolves with the pressed action's id, or `null` when
|
|
437
|
+
* the player closed it without choosing.
|
|
438
|
+
*/
|
|
439
|
+
alert(notification: NotificationInput): Promise<string | null>;
|
|
440
|
+
/** Take a toast or a waiting alert away (an alert resolves with `null`). */
|
|
441
|
+
dismiss(id: string): void;
|
|
442
|
+
}
|
|
443
|
+
|
|
279
444
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
280
445
|
type ModuleType = "game" | "app" | "ai-app";
|
|
281
446
|
/**
|
|
@@ -328,10 +493,19 @@ interface ModuleManifest {
|
|
|
328
493
|
*/
|
|
329
494
|
type?: "template" | "feature";
|
|
330
495
|
/**
|
|
331
|
-
* Part of the
|
|
332
|
-
*
|
|
496
|
+
* Part of the base: the platform installs this system into EVERY new project together with the base
|
|
497
|
+
* (sign-in + the lobby) — only profile, inventory, invite, friends, quests and mailbox (owner's rule
|
|
498
|
+
* 29.09.2026). Every other module is installed only when it is needed: a game brings the ones it
|
|
499
|
+
* `uses`, the AI Coder installs any other one as soon as the task needs what it `provides`. A creator removes it like any module.
|
|
333
500
|
*/
|
|
334
501
|
inBase?: boolean;
|
|
502
|
+
/**
|
|
503
|
+
* A game's modules: catalog ids of the systems this game plugs into (`ctx.multiplayer` →
|
|
504
|
+
* `"multiplayer"`, `ctx.content` → `"workshop"`, …). The platform installs them together with the game
|
|
505
|
+
* — a new title on this game gets them, and so does a project the AI Coder adds the game to.
|
|
506
|
+
* `npm run check` makes a module that registers in `ctx.multiplayer` / `ctx.content` list them.
|
|
507
|
+
*/
|
|
508
|
+
uses?: string[];
|
|
335
509
|
/** Overrides the catalog display name; defaults to `ModuleMeta.name` from `module.ts`. */
|
|
336
510
|
name?: string;
|
|
337
511
|
/** One-line pitch shown on the catalog card. */
|
|
@@ -431,6 +605,12 @@ interface ModuleContext {
|
|
|
431
605
|
* rather than a mode of its own; `open(id)` opens another module's screen. See `FeatureDefinition`.
|
|
432
606
|
*/
|
|
433
607
|
features: FeatureRegistry;
|
|
608
|
+
/**
|
|
609
|
+
* Games that play online and the rooms system that runs them (see `MultiplayerGame`). A game
|
|
610
|
+
* registers here (`registerGame`) and takes the room in `start`; it never creates or joins rooms
|
|
611
|
+
* itself. Without the multiplayer system installed nothing ever calls `start`.
|
|
612
|
+
*/
|
|
613
|
+
multiplayer: MultiplayerRegistry;
|
|
434
614
|
/**
|
|
435
615
|
* The app's modes — every route any module registered, and which one is on screen. The lobby
|
|
436
616
|
* builds its "Play" buttons from it; a game can tell whether a lobby exists to return to.
|
|
@@ -455,6 +635,18 @@ interface ModuleContext {
|
|
|
455
635
|
* @see ModuleAgentApi
|
|
456
636
|
*/
|
|
457
637
|
exposeToAgent(api: ModuleAgentApi): void;
|
|
638
|
+
/**
|
|
639
|
+
* The app's loading screen (see `LoadingReporter`): `ctx.loading.track(work, "Building the world…")`
|
|
640
|
+
* for any long work of the module, so the player never faces a still screen wondering whether the
|
|
641
|
+
* app froze. The host shows it by itself while a scene starts — this is for the module's own work.
|
|
642
|
+
*/
|
|
643
|
+
loading: LoadingReporter;
|
|
644
|
+
/**
|
|
645
|
+
* The app's notifications (see `AppNotifications`): `ctx.notifications.toast({ level: "success",
|
|
646
|
+
* message })` for news, `alert({ … })` for what the player must answer. Never draw your own banner:
|
|
647
|
+
* one look, one queue, one sound for the whole app. The module's own id is the default `source`.
|
|
648
|
+
*/
|
|
649
|
+
notifications: AppNotifications;
|
|
458
650
|
}
|
|
459
651
|
/**
|
|
460
652
|
* A module's debug surface for the agent: read its state, drive it with named actions.
|
|
@@ -509,8 +701,18 @@ interface SceneMountContext {
|
|
|
509
701
|
interface EngineScene {
|
|
510
702
|
/** Preferred host surface. Defaults to `"fullbleed-canvas"` when omitted. */
|
|
511
703
|
readonly surface?: SurfaceKind;
|
|
512
|
-
/**
|
|
704
|
+
/**
|
|
705
|
+
* Create the renderer and attach to the host element. Called once. The host shows its loading
|
|
706
|
+
* screen BEFORE calling it and lets it paint, so heavy synchronous work here (generating terrain,
|
|
707
|
+
* building meshes) never looks like a frozen app.
|
|
708
|
+
*/
|
|
513
709
|
mount(ctx: SceneMountContext): void;
|
|
710
|
+
/**
|
|
711
|
+
* Resolves when the scene is really playable (the world is built, the save is loaded). The host
|
|
712
|
+
* keeps its loading screen up until then. Omit it when the first frame after `mount` is already
|
|
713
|
+
* the game — the host then hides the screen after that frame.
|
|
714
|
+
*/
|
|
715
|
+
ready?(): Promise<void>;
|
|
514
716
|
/** This scene became the active mode — resume RAF / re-acquire input. */
|
|
515
717
|
activate?(): void;
|
|
516
718
|
/** This scene was hidden — pause RAF / release input. Not destroyed; may re-activate. */
|
|
@@ -600,4 +802,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
600
802
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
601
803
|
declare function defineModule(module: Module): Module;
|
|
602
804
|
|
|
603
|
-
export { 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 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 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 };
|
|
805
|
+
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 };
|
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 T(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,
|
|
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 T(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 b={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var M=["currency-bar","wallet","status","account","menu","modes"];function R(e){return typeof e=="string"&&M.includes(e)}function D(e){return e}export{M as SHARED_UI_ROLES,x as TOPIC_NAME_RE,D as defineModule,v as defineTopic,b as hostTopics,R as isSharedUiRole,S as isTopicName,h as shape,T as topicNamespace};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idosgames/module-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|