@datamoc/mw_games 0.1.1 → 0.2.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/README.md +57 -32
- package/dist/actors/Appearances.js +1 -1
- package/dist/actors/Aura.d.ts +29 -0
- package/dist/actors/Aura.d.ts.map +1 -0
- package/dist/actors/Aura.js +69 -0
- package/dist/actors/Aura.js.map +1 -0
- package/dist/actors/ItemStatusEffect.d.ts +27 -0
- package/dist/actors/ItemStatusEffect.d.ts.map +1 -0
- package/dist/actors/ItemStatusEffect.js +35 -0
- package/dist/actors/ItemStatusEffect.js.map +1 -0
- package/dist/actors/Resource.d.ts.map +1 -1
- package/dist/actors/Resource.js +13 -5
- package/dist/actors/Resource.js.map +1 -1
- package/dist/actors/Support.d.ts +32 -0
- package/dist/actors/Support.d.ts.map +1 -0
- package/dist/actors/Support.js +60 -0
- package/dist/actors/Support.js.map +1 -0
- package/dist/actors/Traits.d.ts +29 -0
- package/dist/actors/Traits.d.ts.map +1 -0
- package/dist/actors/Traits.js +21 -0
- package/dist/actors/Traits.js.map +1 -0
- package/dist/actors/craft.d.ts.map +1 -1
- package/dist/actors/craft.js +6 -2
- package/dist/actors/craft.js.map +1 -1
- package/dist/actors/index.d.ts +8 -0
- package/dist/actors/index.d.ts.map +1 -1
- package/dist/actors/index.js +4 -0
- package/dist/actors/index.js.map +1 -1
- package/dist/assets/Streaming.d.ts +40 -0
- package/dist/assets/Streaming.d.ts.map +1 -0
- package/dist/assets/Streaming.js +79 -0
- package/dist/assets/Streaming.js.map +1 -0
- package/dist/assets/index.d.ts +14 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -1
- package/dist/audio/Captions.d.ts +15 -0
- package/dist/audio/Captions.d.ts.map +1 -0
- package/dist/audio/Captions.js +4 -0
- package/dist/audio/Captions.js.map +1 -0
- package/dist/audio/Midi.d.ts +75 -0
- package/dist/audio/Midi.d.ts.map +1 -0
- package/dist/audio/Midi.js +186 -0
- package/dist/audio/Midi.js.map +1 -0
- package/dist/audio/Orchestrator.d.ts +40 -0
- package/dist/audio/Orchestrator.d.ts.map +1 -0
- package/dist/audio/Orchestrator.js +48 -0
- package/dist/audio/Orchestrator.js.map +1 -0
- package/dist/audio/Sound.d.ts +3 -0
- package/dist/audio/Sound.d.ts.map +1 -1
- package/dist/audio/Sound.js +4 -0
- package/dist/audio/Sound.js.map +1 -1
- package/dist/audio/Synth.d.ts +35 -0
- package/dist/audio/Synth.d.ts.map +1 -0
- package/dist/audio/Synth.js +96 -0
- package/dist/audio/Synth.js.map +1 -0
- package/dist/audio/index.d.ts +8 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +4 -0
- package/dist/audio/index.js.map +1 -1
- package/dist/battle/Party.d.ts.map +1 -1
- package/dist/battle/Party.js +2 -0
- package/dist/battle/Party.js.map +1 -1
- package/dist/board/Army.d.ts +17 -0
- package/dist/board/Army.d.ts.map +1 -0
- package/dist/board/Army.js +48 -0
- package/dist/board/Army.js.map +1 -0
- package/dist/board/Classics.d.ts.map +1 -1
- package/dist/board/Classics.js +14 -6
- package/dist/board/Classics.js.map +1 -1
- package/dist/board/FogOfWar.d.ts +23 -0
- package/dist/board/FogOfWar.d.ts.map +1 -0
- package/dist/board/FogOfWar.js +40 -0
- package/dist/board/FogOfWar.js.map +1 -0
- package/dist/board/Tactics.d.ts +2 -0
- package/dist/board/Tactics.d.ts.map +1 -1
- package/dist/board/Tactics.js +36 -8
- package/dist/board/Tactics.js.map +1 -1
- package/dist/board/index.d.ts +5 -1
- package/dist/board/index.d.ts.map +1 -1
- package/dist/board/index.js +3 -1
- package/dist/board/index.js.map +1 -1
- package/dist/core/Feedback.d.ts +23 -0
- package/dist/core/Feedback.d.ts.map +1 -0
- package/dist/core/Feedback.js +35 -0
- package/dist/core/Feedback.js.map +1 -0
- package/dist/core/Game.d.ts +27 -1
- package/dist/core/Game.d.ts.map +1 -1
- package/dist/core/Game.js +61 -5
- package/dist/core/Game.js.map +1 -1
- package/dist/core/Input.d.ts +59 -1
- package/dist/core/Input.d.ts.map +1 -1
- package/dist/core/Input.js +135 -0
- package/dist/core/Input.js.map +1 -1
- package/dist/core/Loading.d.ts +48 -0
- package/dist/core/Loading.d.ts.map +1 -0
- package/dist/core/Loading.js +102 -0
- package/dist/core/Loading.js.map +1 -0
- package/dist/core/News.d.ts +46 -0
- package/dist/core/News.d.ts.map +1 -0
- package/dist/core/News.js +79 -0
- package/dist/core/News.js.map +1 -0
- package/dist/core/PlayerInput.d.ts +38 -0
- package/dist/core/PlayerInput.d.ts.map +1 -0
- package/dist/core/PlayerInput.js +57 -0
- package/dist/core/PlayerInput.js.map +1 -0
- package/dist/core/Random.d.ts +2 -2
- package/dist/core/Random.js +2 -2
- package/dist/core/RunHistory.d.ts +43 -0
- package/dist/core/RunHistory.d.ts.map +1 -0
- package/dist/core/RunHistory.js +41 -0
- package/dist/core/RunHistory.js.map +1 -0
- package/dist/core/Sanitize.d.ts +56 -0
- package/dist/core/Sanitize.d.ts.map +1 -0
- package/dist/core/Sanitize.js +87 -0
- package/dist/core/Sanitize.js.map +1 -0
- package/dist/core/Save.d.ts +31 -7
- package/dist/core/Save.d.ts.map +1 -1
- package/dist/core/Save.js +56 -0
- package/dist/core/Save.js.map +1 -1
- package/dist/core/SaveSync.d.ts +26 -0
- package/dist/core/SaveSync.d.ts.map +1 -0
- package/dist/core/SaveSync.js +52 -0
- package/dist/core/SaveSync.js.map +1 -0
- package/dist/core/Scramble.d.ts +13 -0
- package/dist/core/Scramble.d.ts.map +1 -0
- package/dist/core/Scramble.js +44 -0
- package/dist/core/Scramble.js.map +1 -0
- package/dist/core/Session.d.ts +21 -0
- package/dist/core/Session.d.ts.map +1 -0
- package/dist/core/Session.js +11 -0
- package/dist/core/Session.js.map +1 -0
- package/dist/core/Spawner.d.ts +48 -0
- package/dist/core/Spawner.d.ts.map +1 -0
- package/dist/core/Spawner.js +65 -0
- package/dist/core/Spawner.js.map +1 -0
- package/dist/core/Tween.d.ts +25 -0
- package/dist/core/Tween.d.ts.map +1 -0
- package/dist/core/Tween.js +60 -0
- package/dist/core/Tween.js.map +1 -0
- package/dist/core/UndoHistory.d.ts +37 -0
- package/dist/core/UndoHistory.d.ts.map +1 -0
- package/dist/core/UndoHistory.js +49 -0
- package/dist/core/UndoHistory.js.map +1 -0
- package/dist/core/index.d.ts +22 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -1
- package/dist/i18n/Fluent.d.ts +7 -0
- package/dist/i18n/Fluent.d.ts.map +1 -0
- package/dist/i18n/Fluent.js +86 -0
- package/dist/i18n/Fluent.js.map +1 -0
- package/dist/i18n/index.d.ts +15 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +17 -2
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mw_games.global.js +351 -79
- package/dist/render/Camera.d.ts +32 -4
- package/dist/render/Camera.d.ts.map +1 -1
- package/dist/render/Camera.js +58 -23
- package/dist/render/Camera.js.map +1 -1
- package/dist/render/Capabilities.d.ts +51 -0
- package/dist/render/Capabilities.d.ts.map +1 -0
- package/dist/render/Capabilities.js +56 -0
- package/dist/render/Capabilities.js.map +1 -0
- package/dist/render/ColorBlindness.d.ts +16 -0
- package/dist/render/ColorBlindness.d.ts.map +1 -0
- package/dist/render/ColorBlindness.js +41 -0
- package/dist/render/ColorBlindness.js.map +1 -0
- package/dist/render/ColorTransformBatcher.d.ts +9 -0
- package/dist/render/ColorTransformBatcher.d.ts.map +1 -1
- package/dist/render/ColorTransformBatcher.js +131 -19
- package/dist/render/ColorTransformBatcher.js.map +1 -1
- package/dist/render/Minimap.d.ts +52 -0
- package/dist/render/Minimap.d.ts.map +1 -0
- package/dist/render/Minimap.js +95 -0
- package/dist/render/Minimap.js.map +1 -0
- package/dist/render/TileMap.d.ts +2 -2
- package/dist/render/TileMap.d.ts.map +1 -1
- package/dist/render/TileMap.js +12 -12
- package/dist/render/TileMap.js.map +1 -1
- package/dist/render/TintedSprite.d.ts +1 -8
- package/dist/render/TintedSprite.d.ts.map +1 -1
- package/dist/render/TintedSprite.js +3 -111
- package/dist/render/TintedSprite.js.map +1 -1
- package/dist/render/Viewport.d.ts +46 -0
- package/dist/render/Viewport.d.ts.map +1 -0
- package/dist/render/Viewport.js +52 -0
- package/dist/render/Viewport.js.map +1 -0
- package/dist/render/index.d.ts +9 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +5 -1
- package/dist/render/index.js.map +1 -1
- package/dist/roguelike/FieldOfView.d.ts +1 -1
- package/dist/roguelike/Level.d.ts +2 -2
- package/dist/roguelike/Level.d.ts.map +1 -1
- package/dist/roguelike/Level.js +1 -1
- package/dist/roguelike/Level.js.map +1 -1
- package/dist/roguelike/Pathfinder.d.ts +3 -3
- package/dist/roguelike/Pathfinder.js +2 -2
- package/dist/roguelike/Scheduler.d.ts +1 -1
- package/dist/roguelike/Scheduler.js +1 -1
- package/dist/roguelike/Stealth.d.ts +34 -0
- package/dist/roguelike/Stealth.d.ts.map +1 -0
- package/dist/roguelike/Stealth.js +35 -0
- package/dist/roguelike/Stealth.js.map +1 -0
- package/dist/roguelike/generate.d.ts +3 -3
- package/dist/roguelike/generate.js +1 -1
- package/dist/roguelike/generate.js.map +1 -1
- package/dist/roguelike/index.d.ts +2 -0
- package/dist/roguelike/index.d.ts.map +1 -1
- package/dist/roguelike/index.js +1 -0
- package/dist/roguelike/index.js.map +1 -1
- package/dist/rpg/Collision.d.ts +42 -0
- package/dist/rpg/Collision.d.ts.map +1 -0
- package/dist/rpg/Collision.js +77 -0
- package/dist/rpg/Collision.js.map +1 -0
- package/dist/rpg/FreeMover.d.ts +45 -0
- package/dist/rpg/FreeMover.d.ts.map +1 -0
- package/dist/rpg/FreeMover.js +68 -0
- package/dist/rpg/FreeMover.js.map +1 -0
- package/dist/rpg/Marshal.d.ts +65 -0
- package/dist/rpg/Marshal.d.ts.map +1 -0
- package/dist/rpg/Marshal.js +393 -0
- package/dist/rpg/Marshal.js.map +1 -0
- package/dist/rpg/Quest.d.ts +41 -0
- package/dist/rpg/Quest.d.ts.map +1 -1
- package/dist/rpg/Quest.js +39 -6
- package/dist/rpg/Quest.js.map +1 -1
- package/dist/rpg/index.d.ts +7 -1
- package/dist/rpg/index.d.ts.map +1 -1
- package/dist/rpg/index.js +3 -0
- package/dist/rpg/index.js.map +1 -1
- package/dist/stage/DialogueStage.d.ts +2 -3
- package/dist/stage/DialogueStage.d.ts.map +1 -1
- package/dist/stage/DialogueStage.js +21 -30
- package/dist/stage/DialogueStage.js.map +1 -1
- package/dist/stage/index.d.ts +1 -1
- package/dist/stage/index.d.ts.map +1 -1
- package/dist/stage/script.d.ts +32 -3
- package/dist/stage/script.d.ts.map +1 -1
- package/dist/stage/script.js +0 -0
- package/dist/stage/script.js.map +1 -1
- package/dist/three-d/Character3D.d.ts +48 -0
- package/dist/three-d/Character3D.d.ts.map +1 -0
- package/dist/three-d/Character3D.js +96 -0
- package/dist/three-d/Character3D.js.map +1 -0
- package/dist/three-d/Engine3D.d.ts +23 -0
- package/dist/three-d/Engine3D.d.ts.map +1 -0
- package/dist/three-d/Engine3D.js +45 -0
- package/dist/three-d/Engine3D.js.map +1 -0
- package/dist/three-d/Grid.d.ts +14 -0
- package/dist/three-d/Grid.d.ts.map +1 -0
- package/dist/three-d/Grid.js +13 -0
- package/dist/three-d/Grid.js.map +1 -0
- package/dist/three-d/Heightmap.d.ts +35 -0
- package/dist/three-d/Heightmap.d.ts.map +1 -0
- package/dist/three-d/Heightmap.js +29 -0
- package/dist/three-d/Heightmap.js.map +1 -0
- package/dist/three-d/Models.d.ts +12 -0
- package/dist/three-d/Models.d.ts.map +1 -0
- package/dist/three-d/Models.js +14 -0
- package/dist/three-d/Models.js.map +1 -0
- package/dist/three-d/TileGrid3D.d.ts +21 -0
- package/dist/three-d/TileGrid3D.d.ts.map +1 -0
- package/dist/three-d/TileGrid3D.js +65 -0
- package/dist/three-d/TileGrid3D.js.map +1 -0
- package/dist/three-d/Vox.d.ts +24 -0
- package/dist/three-d/Vox.d.ts.map +1 -0
- package/dist/three-d/Vox.js +93 -0
- package/dist/three-d/Vox.js.map +1 -0
- package/dist/three-d/index.d.ts +13 -0
- package/dist/three-d/index.d.ts.map +1 -0
- package/dist/three-d/index.js +7 -0
- package/dist/three-d/index.js.map +1 -0
- package/dist/ui/Bar.d.ts +49 -0
- package/dist/ui/Bar.d.ts.map +1 -0
- package/dist/ui/Bar.js +70 -0
- package/dist/ui/Bar.js.map +1 -0
- package/dist/ui/BitmapLabel.d.ts +42 -0
- package/dist/ui/BitmapLabel.d.ts.map +1 -0
- package/dist/ui/BitmapLabel.js +59 -0
- package/dist/ui/BitmapLabel.js.map +1 -0
- package/dist/ui/Button.d.ts +52 -0
- package/dist/ui/Button.d.ts.map +1 -0
- package/dist/ui/Button.js +147 -0
- package/dist/ui/Button.js.map +1 -0
- package/dist/ui/FloatingText.d.ts +28 -0
- package/dist/ui/FloatingText.d.ts.map +1 -0
- package/dist/ui/FloatingText.js +39 -0
- package/dist/ui/FloatingText.js.map +1 -0
- package/dist/ui/HelpScreen.d.ts +29 -0
- package/dist/ui/HelpScreen.d.ts.map +1 -0
- package/dist/ui/HelpScreen.js +39 -0
- package/dist/ui/HelpScreen.js.map +1 -0
- package/dist/ui/IconGrid.d.ts +8 -0
- package/dist/ui/IconGrid.d.ts.map +1 -1
- package/dist/ui/IconGrid.js +27 -1
- package/dist/ui/IconGrid.js.map +1 -1
- package/dist/ui/Label.d.ts +10 -1
- package/dist/ui/Label.d.ts.map +1 -1
- package/dist/ui/Label.js +25 -2
- package/dist/ui/Label.js.map +1 -1
- package/dist/ui/ListView.d.ts +12 -1
- package/dist/ui/ListView.d.ts.map +1 -1
- package/dist/ui/ListView.js +56 -2
- package/dist/ui/ListView.js.map +1 -1
- package/dist/ui/LoadingScreen.d.ts +31 -0
- package/dist/ui/LoadingScreen.d.ts.map +1 -0
- package/dist/ui/LoadingScreen.js +60 -0
- package/dist/ui/LoadingScreen.js.map +1 -0
- package/dist/ui/MessageBox.d.ts +27 -1
- package/dist/ui/MessageBox.d.ts.map +1 -1
- package/dist/ui/MessageBox.js +63 -14
- package/dist/ui/MessageBox.js.map +1 -1
- package/dist/ui/RebindScreen.d.ts +55 -0
- package/dist/ui/RebindScreen.d.ts.map +1 -0
- package/dist/ui/RebindScreen.js +114 -0
- package/dist/ui/RebindScreen.js.map +1 -0
- package/dist/ui/Toast.d.ts +44 -0
- package/dist/ui/Toast.d.ts.map +1 -0
- package/dist/ui/Toast.js +89 -0
- package/dist/ui/Toast.js.map +1 -0
- package/dist/ui/VerticalLabel.d.ts +5 -0
- package/dist/ui/VerticalLabel.d.ts.map +1 -1
- package/dist/ui/VerticalLabel.js +18 -7
- package/dist/ui/VerticalLabel.js.map +1 -1
- package/dist/ui/Window.d.ts +6 -0
- package/dist/ui/Window.d.ts.map +1 -1
- package/dist/ui/Window.js +16 -1
- package/dist/ui/Window.js.map +1 -1
- package/dist/ui/WindowStack.d.ts +1 -0
- package/dist/ui/WindowStack.d.ts.map +1 -1
- package/dist/ui/WindowStack.js +4 -1
- package/dist/ui/WindowStack.js.map +1 -1
- package/dist/ui/index.d.ts +17 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +9 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/theme.d.ts +21 -0
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +42 -0
- package/dist/ui/theme.js.map +1 -1
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/dist/world/Environment.d.ts +35 -0
- package/dist/world/Environment.d.ts.map +1 -0
- package/dist/world/Environment.js +55 -0
- package/dist/world/Environment.js.map +1 -0
- package/dist/world/index.d.ts +2 -0
- package/dist/world/index.d.ts.map +1 -1
- package/dist/world/index.js +1 -0
- package/dist/world/index.js.map +1 -1
- package/package.json +40 -4
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type SaveStorage } from './Save.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches a game's own news/announcements feed - the inbound counterpart to
|
|
4
|
+
* `FeedbackClient`, following the same injectable-transport shape (a game's own HTTPS
|
|
5
|
+
* endpoint, no backend shipped or assumed) rather than a second pattern.
|
|
6
|
+
*/
|
|
7
|
+
export interface NewsItem {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
publishedAt?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface NewsOptions {
|
|
14
|
+
endpoint: string;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
fetch?: typeof globalThis.fetch;
|
|
17
|
+
}
|
|
18
|
+
export declare class NewsClient {
|
|
19
|
+
private readonly endpoint;
|
|
20
|
+
private readonly timeoutMs;
|
|
21
|
+
private readonly fetchFn;
|
|
22
|
+
constructor(options: NewsOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Fetches and validates the feed; a malformed item throws rather than reaching a game as
|
|
25
|
+
* trusted data. The response is read as text and run through `sanitizeInboundText`
|
|
26
|
+
* (a size cap, no embedded control characters) before `JSON.parse` ever sees it, ahead of
|
|
27
|
+
* `normalizeItem`'s own per-field shape check below.
|
|
28
|
+
*/
|
|
29
|
+
fetchItems(): Promise<NewsItem[]>;
|
|
30
|
+
}
|
|
31
|
+
export interface NewsSeenOptions {
|
|
32
|
+
namespace: string;
|
|
33
|
+
storage?: SaveStorage;
|
|
34
|
+
}
|
|
35
|
+
/** which news items a player has already dismissed, so a returning player is not shown the same announcement forever */
|
|
36
|
+
export declare class NewsSeenTracker {
|
|
37
|
+
private readonly storage;
|
|
38
|
+
private readonly key;
|
|
39
|
+
constructor(options: NewsSeenOptions);
|
|
40
|
+
private readSeen;
|
|
41
|
+
isSeen(id: string): boolean;
|
|
42
|
+
markSeen(id: string): void;
|
|
43
|
+
/** `items` filtered down to whichever have not been marked seen yet */
|
|
44
|
+
unseen(items: readonly NewsItem[]): NewsItem[];
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=News.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"News.d.ts","sourceRoot":"","sources":["../../src/core/News.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7D;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC;AAED,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,YAAY,OAAO,EAAE,WAAW,EAO/B;IAED;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAYtC;CACD;AAgBD,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wHAAwH;AACxH,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YAAY,OAAO,EAAE,eAAe,EAGnC;IAED,OAAO,CAAC,QAAQ;IAKhB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE1B;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIzB;IAED,uEAAuE;IACvE,MAAM,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAG7C;CACD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defaultStorage } from './Save.js';
|
|
2
|
+
import { sanitizeInboundText } from './Sanitize.js';
|
|
3
|
+
export class NewsClient {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
if (!options.endpoint)
|
|
6
|
+
throw new Error('news endpoint is required');
|
|
7
|
+
this.endpoint = options.endpoint;
|
|
8
|
+
this.timeoutMs = options.timeoutMs ?? 10000;
|
|
9
|
+
if (!(this.timeoutMs > 0))
|
|
10
|
+
throw new Error('news timeout must be positive');
|
|
11
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
12
|
+
if (!this.fetchFn)
|
|
13
|
+
throw new Error('fetch is unavailable; provide NewsOptions.fetch');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetches and validates the feed; a malformed item throws rather than reaching a game as
|
|
17
|
+
* trusted data. The response is read as text and run through `sanitizeInboundText`
|
|
18
|
+
* (a size cap, no embedded control characters) before `JSON.parse` ever sees it, ahead of
|
|
19
|
+
* `normalizeItem`'s own per-field shape check below.
|
|
20
|
+
*/
|
|
21
|
+
async fetchItems() {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
24
|
+
try {
|
|
25
|
+
const response = await this.fetchFn(this.endpoint, { signal: controller.signal });
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error(`news request failed with HTTP ${response.status}`);
|
|
28
|
+
const data = JSON.parse(sanitizeInboundText(await response.text()));
|
|
29
|
+
if (!Array.isArray(data))
|
|
30
|
+
throw new Error('news response was not an array');
|
|
31
|
+
return data.map((raw, index) => normalizeItem(raw, index));
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function normalizeItem(raw, index) {
|
|
39
|
+
if (typeof raw !== 'object' || raw === null)
|
|
40
|
+
throw new Error(`news item ${index} was not an object`);
|
|
41
|
+
const item = raw;
|
|
42
|
+
if (typeof item.id !== 'string' || !item.id)
|
|
43
|
+
throw new Error(`news item ${index} is missing a string id`);
|
|
44
|
+
if (typeof item.title !== 'string')
|
|
45
|
+
throw new Error(`news item ${index} is missing a string title`);
|
|
46
|
+
if (typeof item.body !== 'string')
|
|
47
|
+
throw new Error(`news item ${index} is missing a string body`);
|
|
48
|
+
return {
|
|
49
|
+
id: item.id,
|
|
50
|
+
title: item.title,
|
|
51
|
+
body: item.body,
|
|
52
|
+
publishedAt: typeof item.publishedAt === 'number' ? item.publishedAt : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** which news items a player has already dismissed, so a returning player is not shown the same announcement forever */
|
|
56
|
+
export class NewsSeenTracker {
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.storage = options.storage ?? defaultStorage();
|
|
59
|
+
this.key = `mwg-news-seen:${options.namespace}`;
|
|
60
|
+
}
|
|
61
|
+
readSeen() {
|
|
62
|
+
const raw = this.storage.read(this.key);
|
|
63
|
+
return new Set(raw ? JSON.parse(raw) : []);
|
|
64
|
+
}
|
|
65
|
+
isSeen(id) {
|
|
66
|
+
return this.readSeen().has(id);
|
|
67
|
+
}
|
|
68
|
+
markSeen(id) {
|
|
69
|
+
const seen = this.readSeen();
|
|
70
|
+
seen.add(id);
|
|
71
|
+
this.storage.write(this.key, JSON.stringify([...seen]));
|
|
72
|
+
}
|
|
73
|
+
/** `items` filtered down to whichever have not been marked seen yet */
|
|
74
|
+
unseen(items) {
|
|
75
|
+
const seen = this.readSeen();
|
|
76
|
+
return items.filter((item) => !seen.has(item.id));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=News.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"News.js","sourceRoot":"","sources":["../../src/core/News.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Action } from './Input.ts';
|
|
2
|
+
export interface PlayerInputOptions {
|
|
3
|
+
/** this player's own gamepad, when they have one; omit for a keyboard-only player */
|
|
4
|
+
padIndex?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A player-scoped view over `mwg/core`'s `Input` module for same-screen local multiplayer.
|
|
8
|
+
*
|
|
9
|
+
* `Input` has no concept of a player: `Action` is a bare string, and its `bindings`/`held`/
|
|
10
|
+
* `onAction` all live at module scope. Binding the same action name to two pads directly -
|
|
11
|
+
* `Input.bindButton('confirm', 0, [0])` and `Input.bindButton('confirm', 1, [0])` - does not
|
|
12
|
+
* give two independent "confirm"s; both bindings land in the very same action's key set, so
|
|
13
|
+
* either pad pressing it fires the one shared `onAction`, with no way to tell which pad did.
|
|
14
|
+
*
|
|
15
|
+
* This does not change `Input` itself - it is a thin, purely additive naming convention:
|
|
16
|
+
* every action a `PlayerInput` binds or queries is transparently prefixed with this
|
|
17
|
+
* player's own `id`, so two players' "confirm" become two distinct actions
|
|
18
|
+
* (`"p1:confirm"`, `"p2:confirm"`) that never collide. A single-player game using `Input`'s
|
|
19
|
+
* bare action names directly is entirely unaffected.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PlayerInput {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly padIndex?: number;
|
|
24
|
+
constructor(id: string, options?: PlayerInputOptions);
|
|
25
|
+
private scoped;
|
|
26
|
+
/** binds this player's own action to keyboard keys, independent of any other player's binding for the same action name */
|
|
27
|
+
bind(action: Action, keys: readonly string[]): void;
|
|
28
|
+
/** binds this player's own action to a button on their own `padIndex` */
|
|
29
|
+
bindButton(action: Action, buttons: readonly number[]): void;
|
|
30
|
+
/** binds this player's own action to an axis direction on their own `padIndex` */
|
|
31
|
+
bindAxis(action: Action, axis: number, direction: 1 | -1): void;
|
|
32
|
+
isDown(action: Action): boolean;
|
|
33
|
+
justPressed(action: Action): boolean;
|
|
34
|
+
justReleased(action: Action): boolean;
|
|
35
|
+
/** every key/pad binding for this player's own action, in the same shape `Input.exportBindings` uses */
|
|
36
|
+
keysFor(action: Action): string[];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=PlayerInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayerInput.d.ts","sourceRoot":"","sources":["../../src/core/PlayerInput.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IAClC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAW;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,YAAY,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,EAIvD;IAED,OAAO,CAAC,MAAM;IAId,0HAA0H;IAC1H,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAElD;IAED,yEAAyE;IACzE,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAG3D;IAED,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAG9D;IAED,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9B;IAED,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnC;IAED,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpC;IAED,wGAAwG;IACxG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhC;CACD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as Input from './Input.js';
|
|
2
|
+
/**
|
|
3
|
+
* A player-scoped view over `mwg/core`'s `Input` module for same-screen local multiplayer.
|
|
4
|
+
*
|
|
5
|
+
* `Input` has no concept of a player: `Action` is a bare string, and its `bindings`/`held`/
|
|
6
|
+
* `onAction` all live at module scope. Binding the same action name to two pads directly -
|
|
7
|
+
* `Input.bindButton('confirm', 0, [0])` and `Input.bindButton('confirm', 1, [0])` - does not
|
|
8
|
+
* give two independent "confirm"s; both bindings land in the very same action's key set, so
|
|
9
|
+
* either pad pressing it fires the one shared `onAction`, with no way to tell which pad did.
|
|
10
|
+
*
|
|
11
|
+
* This does not change `Input` itself - it is a thin, purely additive naming convention:
|
|
12
|
+
* every action a `PlayerInput` binds or queries is transparently prefixed with this
|
|
13
|
+
* player's own `id`, so two players' "confirm" become two distinct actions
|
|
14
|
+
* (`"p1:confirm"`, `"p2:confirm"`) that never collide. A single-player game using `Input`'s
|
|
15
|
+
* bare action names directly is entirely unaffected.
|
|
16
|
+
*/
|
|
17
|
+
export class PlayerInput {
|
|
18
|
+
constructor(id, options = {}) {
|
|
19
|
+
if (!id)
|
|
20
|
+
throw new Error('a player needs a non-empty id');
|
|
21
|
+
this.id = id;
|
|
22
|
+
this.padIndex = options.padIndex;
|
|
23
|
+
}
|
|
24
|
+
scoped(action) {
|
|
25
|
+
return `${this.id}:${action}`;
|
|
26
|
+
}
|
|
27
|
+
/** binds this player's own action to keyboard keys, independent of any other player's binding for the same action name */
|
|
28
|
+
bind(action, keys) {
|
|
29
|
+
Input.bind(this.scoped(action), keys);
|
|
30
|
+
}
|
|
31
|
+
/** binds this player's own action to a button on their own `padIndex` */
|
|
32
|
+
bindButton(action, buttons) {
|
|
33
|
+
if (this.padIndex === undefined)
|
|
34
|
+
throw new Error(`player "${this.id}" has no padIndex to bind a gamepad button to`);
|
|
35
|
+
Input.bindButton(this.scoped(action), this.padIndex, buttons);
|
|
36
|
+
}
|
|
37
|
+
/** binds this player's own action to an axis direction on their own `padIndex` */
|
|
38
|
+
bindAxis(action, axis, direction) {
|
|
39
|
+
if (this.padIndex === undefined)
|
|
40
|
+
throw new Error(`player "${this.id}" has no padIndex to bind a gamepad axis to`);
|
|
41
|
+
Input.bindAxis(this.scoped(action), this.padIndex, axis, direction);
|
|
42
|
+
}
|
|
43
|
+
isDown(action) {
|
|
44
|
+
return Input.isDown(this.scoped(action));
|
|
45
|
+
}
|
|
46
|
+
justPressed(action) {
|
|
47
|
+
return Input.justPressed(this.scoped(action));
|
|
48
|
+
}
|
|
49
|
+
justReleased(action) {
|
|
50
|
+
return Input.justReleased(this.scoped(action));
|
|
51
|
+
}
|
|
52
|
+
/** every key/pad binding for this player's own action, in the same shape `Input.exportBindings` uses */
|
|
53
|
+
keysFor(action) {
|
|
54
|
+
return Input.keysFor(this.scoped(action));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=PlayerInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayerInput.js","sourceRoot":"","sources":["../../src/core/PlayerInput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAQpC;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,WAAW;IAIvB,YAAY,EAAU,EAAE,OAAO,GAAuB,EAAE;QACvD,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,CAAC;IAEO,MAAM,CAAC,MAAc;QAC5B,OAAO,GAAG,IAAI,CAAC,EAAE,IAAI,MAAM,EAAE,CAAC;IAC/B,CAAC;IAED,0HAA0H;IAC1H,IAAI,CAAC,MAAc,EAAE,IAAuB;QAC3C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,yEAAyE;IACzE,UAAU,CAAC,MAAc,EAAE,OAA0B;QACpD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,CAAC,EAAE,+CAA+C,CAAC,CAAC;QACpH,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/D,CAAC;IAED,kFAAkF;IAClF,QAAQ,CAAC,MAAc,EAAE,IAAY,EAAE,SAAiB;QACvD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,CAAC,EAAE,6CAA6C,CAAC,CAAC;QAClH,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,CAAC,MAAc;QACpB,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,WAAW,CAAC,MAAc;QACzB,OAAO,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,YAAY,CAAC,MAAc;QAC1B,OAAO,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,wGAAwG;IACxG,OAAO,CAAC,MAAc;QACrB,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;CACD"}
|
package/dist/core/Random.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Deterministic random numbers.
|
|
3
3
|
*
|
|
4
4
|
* A roguelike lives or dies on this: the same seed has to produce the same dungeon, on
|
|
5
|
-
* any machine, in any browser, forever. `Math.random()` cannot do that
|
|
5
|
+
* any machine, in any browser, forever. `Math.random()` cannot do that: it is not
|
|
6
6
|
* seedable and its algorithm is not specified.
|
|
7
7
|
*
|
|
8
8
|
* The generator is xoshiro128**, by David Blackman and Sebastiano Vigna, released into the
|
|
@@ -45,7 +45,7 @@ export declare function reset(): void;
|
|
|
45
45
|
export declare function float(min?: number, max?: number): number;
|
|
46
46
|
/** an integer in [0, max), or [min, max) */
|
|
47
47
|
export declare function int(min: number, max?: number): number;
|
|
48
|
-
/** an integer in [min, max], both ends included
|
|
48
|
+
/** an integer in [min, max], both ends included (the usual shape for dice) */
|
|
49
49
|
export declare function range(min: number, max: number): number;
|
|
50
50
|
/**
|
|
51
51
|
* A triangular distribution over [min, max], peaking in the middle.
|
package/dist/core/Random.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Deterministic random numbers.
|
|
3
3
|
*
|
|
4
4
|
* A roguelike lives or dies on this: the same seed has to produce the same dungeon, on
|
|
5
|
-
* any machine, in any browser, forever. `Math.random()` cannot do that
|
|
5
|
+
* any machine, in any browser, forever. `Math.random()` cannot do that: it is not
|
|
6
6
|
* seedable and its algorithm is not specified.
|
|
7
7
|
*
|
|
8
8
|
* The generator is xoshiro128**, by David Blackman and Sebastiano Vigna, released into the
|
|
@@ -128,7 +128,7 @@ export function int(min, max) {
|
|
|
128
128
|
return current().int(min);
|
|
129
129
|
return min + current().int(max - min);
|
|
130
130
|
}
|
|
131
|
-
/** an integer in [min, max], both ends included
|
|
131
|
+
/** an integer in [min, max], both ends included (the usual shape for dice) */
|
|
132
132
|
export function range(min, max) {
|
|
133
133
|
return min + current().int(max - min + 1);
|
|
134
134
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type SaveStorage } from './Save.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A personal, local record of completed runs - what `core.Session` (launch count) and
|
|
4
|
+
* `core.Achievements` (derived unlocks, no per-run record) don't keep, and distinct from
|
|
5
|
+
* `SaveSystem.list()`, which enumerates *continuable* slots, not runs already over.
|
|
6
|
+
*
|
|
7
|
+
* `mwg` stays agnostic about what a run's own report actually contains, the same way
|
|
8
|
+
* `SaveSystem` stays agnostic about what "the save" contains: a game supplies its own
|
|
9
|
+
* `summary` shape (score, cause of death, floor reached, whatever it considers a run's own
|
|
10
|
+
* stats) and this only ever stores, lists, and sorts it.
|
|
11
|
+
*/
|
|
12
|
+
export interface RunHistoryEntry<T> {
|
|
13
|
+
id: string;
|
|
14
|
+
endedAt: number;
|
|
15
|
+
summary: T;
|
|
16
|
+
}
|
|
17
|
+
export interface RunHistoryOptions {
|
|
18
|
+
/** namespaces the stored history, so two games sharing an origin never collide */
|
|
19
|
+
namespace: string;
|
|
20
|
+
storage?: SaveStorage;
|
|
21
|
+
/** oldest runs are dropped once this many are stored; omit for unlimited */
|
|
22
|
+
limit?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class RunHistory<T> {
|
|
25
|
+
private readonly storage;
|
|
26
|
+
private readonly limit?;
|
|
27
|
+
private readonly key;
|
|
28
|
+
constructor(options: RunHistoryOptions);
|
|
29
|
+
private readAll;
|
|
30
|
+
/** appends a completed run's summary, oldest first; drops the oldest entry once `limit` is exceeded */
|
|
31
|
+
record(summary: T): RunHistoryEntry<T>;
|
|
32
|
+
/** every recorded run, oldest first */
|
|
33
|
+
all(): readonly RunHistoryEntry<T>[];
|
|
34
|
+
/**
|
|
35
|
+
* Every recorded run sorted by a field of its own summary - explicitly a local, personal
|
|
36
|
+
* ranking over this player's own history, not a networked leaderboard comparing players
|
|
37
|
+
* against each other, which `mwg` does not attempt here.
|
|
38
|
+
*/
|
|
39
|
+
ranked(by: (summary: T) => number, order?: 'asc' | 'desc'): readonly RunHistoryEntry<T>[];
|
|
40
|
+
/** drops every recorded run */
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=RunHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RunHistory.d.ts","sourceRoot":"","sources":["../../src/core/RunHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7D;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC;CACX;AAED,MAAM,WAAW,iBAAiB;IACjC,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,UAAU,CAAC,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YAAY,OAAO,EAAE,iBAAiB,EAIrC;IAED,OAAO,CAAC,OAAO;IAKf,uGAAuG;IACvG,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAOrC;IAED,uCAAuC;IACvC,GAAG,IAAI,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAEnC;IAED;;;;OAIG;IACH,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,EAAE,KAAK,GAAE,KAAK,GAAG,MAAe,GAAG,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAIhG;IAED,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAEZ;CACD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defaultStorage } from './Save.js';
|
|
2
|
+
export class RunHistory {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.storage = options.storage ?? defaultStorage();
|
|
5
|
+
this.limit = options.limit;
|
|
6
|
+
this.key = `mwg-runs:${options.namespace}`;
|
|
7
|
+
}
|
|
8
|
+
readAll() {
|
|
9
|
+
const raw = this.storage.read(this.key);
|
|
10
|
+
return raw ? JSON.parse(raw) : [];
|
|
11
|
+
}
|
|
12
|
+
/** appends a completed run's summary, oldest first; drops the oldest entry once `limit` is exceeded */
|
|
13
|
+
record(summary) {
|
|
14
|
+
const entries = this.readAll();
|
|
15
|
+
const entry = { id: `${Date.now()}-${entries.length}`, endedAt: Date.now(), summary };
|
|
16
|
+
entries.push(entry);
|
|
17
|
+
if (this.limit !== undefined && entries.length > this.limit)
|
|
18
|
+
entries.splice(0, entries.length - this.limit);
|
|
19
|
+
this.storage.write(this.key, JSON.stringify(entries));
|
|
20
|
+
return entry;
|
|
21
|
+
}
|
|
22
|
+
/** every recorded run, oldest first */
|
|
23
|
+
all() {
|
|
24
|
+
return this.readAll();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Every recorded run sorted by a field of its own summary - explicitly a local, personal
|
|
28
|
+
* ranking over this player's own history, not a networked leaderboard comparing players
|
|
29
|
+
* against each other, which `mwg` does not attempt here.
|
|
30
|
+
*/
|
|
31
|
+
ranked(by, order = 'desc') {
|
|
32
|
+
const entries = this.readAll();
|
|
33
|
+
const sign = order === 'desc' ? -1 : 1;
|
|
34
|
+
return entries.sort((a, b) => sign * (by(a.summary) - by(b.summary)));
|
|
35
|
+
}
|
|
36
|
+
/** drops every recorded run */
|
|
37
|
+
clear() {
|
|
38
|
+
this.storage.remove(this.key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=RunHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RunHistory.js","sourceRoot":"","sources":["../../src/core/RunHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AA0B7D,MAAM,OAAO,UAAU;IAKtB,YAAY,OAA0B;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,cAAc,EAAE,CAAC;QACnD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,GAAG,GAAG,YAAY,OAAO,CAAC,SAAS,EAAE,CAAC;IAC5C,CAAC;IAEO,OAAO;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,uGAAuG;IACvG,MAAM,CAAC,OAAU;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAuB,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;QAC1G,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5G,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,uCAAuC;IACvC,GAAG;QACF,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,EAA0B,EAAE,KAAK,GAAmB,MAAM;QAChE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,+BAA+B;IAC/B,KAAK;QACJ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;CACD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cheap, structural checks over inbound data - a save imported from another device or
|
|
3
|
+
* server (`SaveSystem.importSlot`), a news feed response (`NewsClient`), an external save
|
|
4
|
+
* format (`rpg.decodeMarshal`) - run before any of it is parsed or handed to a game as
|
|
5
|
+
* trusted state. Deliberately narrow: a size cap and a control-character check catch a
|
|
6
|
+
* truncated, corrupted, or hostile payload cheaply, ahead of and distinct from validating
|
|
7
|
+
* that a parsed value's own fields have the shape a game actually expects (`validateSchema`,
|
|
8
|
+
* below), which is a separate, deeper pass over already-parsed data.
|
|
9
|
+
*/
|
|
10
|
+
export interface SizeLimitOptions {
|
|
11
|
+
/** bytes; defaults to 10 MB */
|
|
12
|
+
maxBytes?: number;
|
|
13
|
+
}
|
|
14
|
+
/** throws once `data`'s size (UTF-8 byte length for a string, byte length for a buffer) exceeds the cap */
|
|
15
|
+
export declare function checkSize(data: string | Uint8Array, options?: SizeLimitOptions): void;
|
|
16
|
+
/**
|
|
17
|
+
* Throws on an embedded NUL byte or a control character outside ordinary whitespace
|
|
18
|
+
* (tab, newline, carriage return) - the kind of byte a well-formed save or news response
|
|
19
|
+
* never legitimately contains, and a cheap tell that a payload was truncated, corrupted, or
|
|
20
|
+
* deliberately malformed before it ever reaches `JSON.parse`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkNoControlCharacters(text: string): void;
|
|
23
|
+
/** `checkSize` then `checkNoControlCharacters` - the minimum bar before any inbound text is parsed */
|
|
24
|
+
export declare function sanitizeInboundText(text: string, options?: SizeLimitOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* A small schema for validating an already-parsed value's shape - deliberately not a full
|
|
27
|
+
* JSON Schema implementation, only what `SaveSystem`/`NewsClient`-shaped data actually needs:
|
|
28
|
+
* primitives, arrays, and objects, with `__proto__`/`constructor`/`prototype` keys always
|
|
29
|
+
* rejected regardless of what a schema itself asks for, since no legitimate save or feed
|
|
30
|
+
* payload ever needs to set one and a parsed JSON object having one is exactly the
|
|
31
|
+
* prototype-pollution shape this exists to catch.
|
|
32
|
+
*/
|
|
33
|
+
export type Schema = {
|
|
34
|
+
type: 'string';
|
|
35
|
+
optional?: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'number';
|
|
38
|
+
optional?: boolean;
|
|
39
|
+
min?: number;
|
|
40
|
+
max?: number;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'boolean';
|
|
43
|
+
optional?: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'object';
|
|
46
|
+
optional?: boolean;
|
|
47
|
+
fields: Record<string, Schema>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'array';
|
|
50
|
+
optional?: boolean;
|
|
51
|
+
items: Schema;
|
|
52
|
+
maxLength?: number;
|
|
53
|
+
};
|
|
54
|
+
/** throws with a path-qualified message at the first field that does not match `schema` */
|
|
55
|
+
export declare function validateSchema(value: unknown, schema: Schema, path?: string): void;
|
|
56
|
+
//# sourceMappingURL=Sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sanitize.d.ts","sourceRoot":"","sources":["../../src/core/Sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,gBAAgB;IAChC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,2GAA2G;AAC3G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ3D;AAED,sGAAsG;AACtG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAIxF;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GACf;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAI5E,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAoC/E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cheap, structural checks over inbound data - a save imported from another device or
|
|
3
|
+
* server (`SaveSystem.importSlot`), a news feed response (`NewsClient`), an external save
|
|
4
|
+
* format (`rpg.decodeMarshal`) - run before any of it is parsed or handed to a game as
|
|
5
|
+
* trusted state. Deliberately narrow: a size cap and a control-character check catch a
|
|
6
|
+
* truncated, corrupted, or hostile payload cheaply, ahead of and distinct from validating
|
|
7
|
+
* that a parsed value's own fields have the shape a game actually expects (`validateSchema`,
|
|
8
|
+
* below), which is a separate, deeper pass over already-parsed data.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
11
|
+
/** throws once `data`'s size (UTF-8 byte length for a string, byte length for a buffer) exceeds the cap */
|
|
12
|
+
export function checkSize(data, options = {}) {
|
|
13
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
14
|
+
const size = typeof data === 'string' ? new TextEncoder().encode(data).length : data.length;
|
|
15
|
+
if (size > maxBytes)
|
|
16
|
+
throw new Error(`inbound data exceeds the ${maxBytes}-byte limit (${size} bytes)`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Throws on an embedded NUL byte or a control character outside ordinary whitespace
|
|
20
|
+
* (tab, newline, carriage return) - the kind of byte a well-formed save or news response
|
|
21
|
+
* never legitimately contains, and a cheap tell that a payload was truncated, corrupted, or
|
|
22
|
+
* deliberately malformed before it ever reaches `JSON.parse`.
|
|
23
|
+
*/
|
|
24
|
+
export function checkNoControlCharacters(text) {
|
|
25
|
+
for (let i = 0; i < text.length; i++) {
|
|
26
|
+
const code = text.charCodeAt(i);
|
|
27
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d)
|
|
28
|
+
continue; //tab, LF, CR
|
|
29
|
+
if (code <= 0x1f || code === 0x7f) {
|
|
30
|
+
throw new Error(`inbound data contains a control character (0x${code.toString(16).padStart(2, '0')}) at index ${i}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** `checkSize` then `checkNoControlCharacters` - the minimum bar before any inbound text is parsed */
|
|
35
|
+
export function sanitizeInboundText(text, options = {}) {
|
|
36
|
+
checkSize(text, options);
|
|
37
|
+
checkNoControlCharacters(text);
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
41
|
+
/** throws with a path-qualified message at the first field that does not match `schema` */
|
|
42
|
+
export function validateSchema(value, schema, path = '$') {
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
if (schema.optional)
|
|
45
|
+
return;
|
|
46
|
+
throw new Error(`${path} is required`);
|
|
47
|
+
}
|
|
48
|
+
switch (schema.type) {
|
|
49
|
+
case 'string':
|
|
50
|
+
if (typeof value !== 'string')
|
|
51
|
+
throw new Error(`${path} must be a string`);
|
|
52
|
+
return;
|
|
53
|
+
case 'number':
|
|
54
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
55
|
+
throw new Error(`${path} must be a finite number`);
|
|
56
|
+
if (schema.min !== undefined && value < schema.min)
|
|
57
|
+
throw new Error(`${path} must be >= ${schema.min}`);
|
|
58
|
+
if (schema.max !== undefined && value > schema.max)
|
|
59
|
+
throw new Error(`${path} must be <= ${schema.max}`);
|
|
60
|
+
return;
|
|
61
|
+
case 'boolean':
|
|
62
|
+
if (typeof value !== 'boolean')
|
|
63
|
+
throw new Error(`${path} must be a boolean`);
|
|
64
|
+
return;
|
|
65
|
+
case 'array':
|
|
66
|
+
if (!Array.isArray(value))
|
|
67
|
+
throw new Error(`${path} must be an array`);
|
|
68
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
69
|
+
throw new Error(`${path} exceeds its maximum length of ${schema.maxLength}`);
|
|
70
|
+
}
|
|
71
|
+
value.forEach((item, index) => validateSchema(item, schema.items, `${path}[${index}]`));
|
|
72
|
+
return;
|
|
73
|
+
case 'object': {
|
|
74
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
75
|
+
throw new Error(`${path} must be an object`);
|
|
76
|
+
for (const key of Object.keys(value)) {
|
|
77
|
+
if (FORBIDDEN_KEYS.has(key))
|
|
78
|
+
throw new Error(`${path} contains a forbidden key "${key}"`);
|
|
79
|
+
}
|
|
80
|
+
for (const [key, fieldSchema] of Object.entries(schema.fields)) {
|
|
81
|
+
validateSchema(value[key], fieldSchema, `${path}.${key}`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=Sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sanitize.js","sourceRoot":"","sources":["../../src/core/Sanitize.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Save.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Named save slots, versioned, with a preview - built on plain, JSON-serialisable state
|
|
3
|
-
* rather than truly arbitrary object graphs. A game's own classes are expected to flatten
|
|
4
|
-
* themselves the same way `mwg/rpg`'s `GameState` already does (a `toJSON`/reviving
|
|
5
|
-
* constructor pair), not to be serialised by reference with cycles preserved; that is a
|
|
6
|
-
* different, much larger problem than a save system needs to solve.
|
|
7
|
-
*/
|
|
8
1
|
export interface SaveMeta {
|
|
9
2
|
version: number;
|
|
10
3
|
savedAt: number;
|
|
@@ -42,7 +35,38 @@ export declare class SaveSystem<T> {
|
|
|
42
35
|
save(slot: string, state: T, preview?: unknown): void;
|
|
43
36
|
/** reads a slot, migrating it up to the current version if it was saved at an older one */
|
|
44
37
|
load(slot: string): SaveData<T> | null;
|
|
38
|
+
/**
|
|
39
|
+
* Adopts a save this `SaveSystem` never wrote: `normalize` runs once on `externalBytes`,
|
|
40
|
+
* producing state at version 0, which is then carried up through the same `migrations`
|
|
41
|
+
* chain an ordinary `load` already uses to reach the current version - a game supplies
|
|
42
|
+
* one `normalize` per external format it wants to accept (an `rpg.decodeMarshal`-based
|
|
43
|
+
* one for a `Game.rxdata`, say), rather than hand-rolling "decode, then call `save`"
|
|
44
|
+
* outside the versioned pipeline every migration otherwise goes through.
|
|
45
|
+
*
|
|
46
|
+
* `externalBytes` is size-checked before `normalize` ever sees it - a cheap, structural
|
|
47
|
+
* guard against a truncated or hostile file, ahead of and distinct from validating
|
|
48
|
+
* whatever shape `normalize` itself produces.
|
|
49
|
+
*/
|
|
50
|
+
importExternal(slot: string, externalBytes: Uint8Array, normalize: (bytes: Uint8Array) => unknown, preview?: unknown): void;
|
|
45
51
|
delete(slot: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Reads a slot back out as a portable string a player can carry to another browser or
|
|
54
|
+
* device, or hand to their own server - the counterpart to `importSlot`, for `mwg`'s own
|
|
55
|
+
* save shape rather than a foreign one (`importExternal` covers that side). `scrambleKey`,
|
|
56
|
+
* when given, runs the payload through `scramble` first: not encryption, only enough to
|
|
57
|
+
* stop a save being hand-edited in a text editor. Returns null for a slot with no save.
|
|
58
|
+
*/
|
|
59
|
+
exportSlot(slot: string, scrambleKey?: string): string | null;
|
|
60
|
+
/**
|
|
61
|
+
* Writes a payload from this `SaveSystem`'s own `exportSlot` back as an ordinary save,
|
|
62
|
+
* migrated up to the current version the same way `load` migrates an older save found
|
|
63
|
+
* locally. `scrambleKey` must match whatever `exportSlot` scrambled it with, if any.
|
|
64
|
+
*
|
|
65
|
+
* `payload` is size- and control-character-checked before it is unscrambled or parsed -
|
|
66
|
+
* this crossed a device or a server, unlike `load`'s own `localStorage`, which this same
|
|
67
|
+
* `SaveSystem` wrote itself and has no reason to distrust.
|
|
68
|
+
*/
|
|
69
|
+
importSlot(slot: string, payload: string, scrambleKey?: string): void;
|
|
46
70
|
/** every slot with a save, and its metadata - for a save-select screen */
|
|
47
71
|
list(): Array<{
|
|
48
72
|
slot: string;
|
package/dist/core/Save.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Save.d.ts","sourceRoot":"","sources":["../../src/core/Save.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Save.d.ts","sourceRoot":"","sources":["../../src/core/Save.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,CAAC,CAAC;CACT;AAED,iGAAiG;AACjG,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,IAAI,MAAM,EAAE,CAAC;CACjB;AAuBD,qGAAqG;AACrG,wBAAgB,cAAc,IAAI,WAAW,CAS5C;AAED,MAAM,WAAW,iBAAiB;IACjC,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;IACzD,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qBAAa,UAAU,CAAC,CAAC;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,OAAO,CAAc;IAE7B,YAAY,OAAO,EAAE,iBAAiB,EAKrC;IAED,OAAO,CAAC,GAAG;IAIX,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAGpD;IAED,2FAA2F;IAC3F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAWrC;IAED;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAS1H;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzB;IAED;;;;;;OAMG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D;IAED;;;;;;;;OAQG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAWpE;IAED,0EAA0E;IAC1E,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAY9C;CACD"}
|