@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
package/dist/core/Save.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* constructor pair), not to be serialised by reference with cycles preserved; that is a
|
|
6
6
|
* different, much larger problem than a save system needs to solve.
|
|
7
7
|
*/
|
|
8
|
+
import { scramble, unscramble } from './Scramble.js';
|
|
9
|
+
import { checkSize, sanitizeInboundText } from './Sanitize.js';
|
|
8
10
|
/** an in-memory fallback, so saving still works where `localStorage` is unavailable */
|
|
9
11
|
class MemoryStorage {
|
|
10
12
|
constructor() {
|
|
@@ -60,9 +62,63 @@ export class SaveSystem {
|
|
|
60
62
|
}
|
|
61
63
|
return { meta: { ...data.meta, version: this.version }, state: state };
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Adopts a save this `SaveSystem` never wrote: `normalize` runs once on `externalBytes`,
|
|
67
|
+
* producing state at version 0, which is then carried up through the same `migrations`
|
|
68
|
+
* chain an ordinary `load` already uses to reach the current version - a game supplies
|
|
69
|
+
* one `normalize` per external format it wants to accept (an `rpg.decodeMarshal`-based
|
|
70
|
+
* one for a `Game.rxdata`, say), rather than hand-rolling "decode, then call `save`"
|
|
71
|
+
* outside the versioned pipeline every migration otherwise goes through.
|
|
72
|
+
*
|
|
73
|
+
* `externalBytes` is size-checked before `normalize` ever sees it - a cheap, structural
|
|
74
|
+
* guard against a truncated or hostile file, ahead of and distinct from validating
|
|
75
|
+
* whatever shape `normalize` itself produces.
|
|
76
|
+
*/
|
|
77
|
+
importExternal(slot, externalBytes, normalize, preview) {
|
|
78
|
+
checkSize(externalBytes);
|
|
79
|
+
let state = normalize(externalBytes);
|
|
80
|
+
for (let v = 0; v < this.version; v++) {
|
|
81
|
+
state = (this.migrations[v] ?? ((s) => s))(state);
|
|
82
|
+
}
|
|
83
|
+
const data = { meta: { version: this.version, savedAt: Date.now(), preview }, state: state };
|
|
84
|
+
this.storage.write(this.key(slot), JSON.stringify(data));
|
|
85
|
+
}
|
|
63
86
|
delete(slot) {
|
|
64
87
|
this.storage.remove(this.key(slot));
|
|
65
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Reads a slot back out as a portable string a player can carry to another browser or
|
|
91
|
+
* device, or hand to their own server - the counterpart to `importSlot`, for `mwg`'s own
|
|
92
|
+
* save shape rather than a foreign one (`importExternal` covers that side). `scrambleKey`,
|
|
93
|
+
* when given, runs the payload through `scramble` first: not encryption, only enough to
|
|
94
|
+
* stop a save being hand-edited in a text editor. Returns null for a slot with no save.
|
|
95
|
+
*/
|
|
96
|
+
exportSlot(slot, scrambleKey) {
|
|
97
|
+
const raw = this.storage.read(this.key(slot));
|
|
98
|
+
if (raw === null)
|
|
99
|
+
return null;
|
|
100
|
+
return scrambleKey ? scramble(raw, scrambleKey) : raw;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Writes a payload from this `SaveSystem`'s own `exportSlot` back as an ordinary save,
|
|
104
|
+
* migrated up to the current version the same way `load` migrates an older save found
|
|
105
|
+
* locally. `scrambleKey` must match whatever `exportSlot` scrambled it with, if any.
|
|
106
|
+
*
|
|
107
|
+
* `payload` is size- and control-character-checked before it is unscrambled or parsed -
|
|
108
|
+
* this crossed a device or a server, unlike `load`'s own `localStorage`, which this same
|
|
109
|
+
* `SaveSystem` wrote itself and has no reason to distrust.
|
|
110
|
+
*/
|
|
111
|
+
importSlot(slot, payload, scrambleKey) {
|
|
112
|
+
checkSize(payload);
|
|
113
|
+
const raw = scrambleKey ? unscramble(payload, scrambleKey) : payload;
|
|
114
|
+
const data = JSON.parse(sanitizeInboundText(raw));
|
|
115
|
+
let state = data.state;
|
|
116
|
+
for (let v = data.meta.version; v < this.version; v++) {
|
|
117
|
+
state = (this.migrations[v] ?? ((s) => s))(state);
|
|
118
|
+
}
|
|
119
|
+
const normalized = { meta: { ...data.meta, version: this.version }, state: state };
|
|
120
|
+
this.storage.write(this.key(slot), JSON.stringify(normalized));
|
|
121
|
+
}
|
|
66
122
|
/** every slot with a save, and its metadata - for a save-select screen */
|
|
67
123
|
list() {
|
|
68
124
|
const prefix = `mwg-save:${this.namespace}:`;
|
package/dist/core/Save.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Save.js","sourceRoot":"","sources":["../../src/core/Save.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"Save.js","sourceRoot":"","sources":["../../src/core/Save.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uploads/downloads one `SaveSystem.exportSlot` payload to a game's own HTTPS endpoint - the
|
|
3
|
+
* server half of item 124's save export/import, following `FeedbackClient`'s own shape
|
|
4
|
+
* (an injectable transport a game points at its own endpoint) rather than a second pattern:
|
|
5
|
+
* `mwg` ships no backend and assumes none here either.
|
|
6
|
+
*/
|
|
7
|
+
export interface SaveSyncOptions {
|
|
8
|
+
endpoint: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
11
|
+
}
|
|
12
|
+
export interface SaveSyncResponse {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class SaveSyncClient {
|
|
17
|
+
private readonly endpoint;
|
|
18
|
+
private readonly timeoutMs;
|
|
19
|
+
private readonly fetchFn;
|
|
20
|
+
constructor(options: SaveSyncOptions);
|
|
21
|
+
/** sends an `exportSlot` payload to the endpoint, replacing whatever it held before */
|
|
22
|
+
upload(payload: string): Promise<SaveSyncResponse>;
|
|
23
|
+
/** fetches whatever payload the endpoint currently holds, ready for `SaveSystem.importSlot` */
|
|
24
|
+
download(): Promise<string>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=SaveSync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveSync.d.ts","sourceRoot":"","sources":["../../src/core/SaveSync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,YAAY,OAAO,EAAE,eAAe,EAOnC;IAED,uFAAuF;IACjF,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgBvD;IAED,+FAA+F;IACzF,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAYhC;CACD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class SaveSyncClient {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
if (!options.endpoint)
|
|
4
|
+
throw new Error('save sync endpoint is required');
|
|
5
|
+
this.endpoint = options.endpoint;
|
|
6
|
+
this.timeoutMs = options.timeoutMs ?? 10000;
|
|
7
|
+
if (!(this.timeoutMs > 0))
|
|
8
|
+
throw new Error('save sync timeout must be positive');
|
|
9
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
10
|
+
if (!this.fetchFn)
|
|
11
|
+
throw new Error('fetch is unavailable; provide SaveSyncOptions.fetch');
|
|
12
|
+
}
|
|
13
|
+
/** sends an `exportSlot` payload to the endpoint, replacing whatever it held before */
|
|
14
|
+
async upload(payload) {
|
|
15
|
+
if (!payload)
|
|
16
|
+
throw new Error('save sync payload cannot be empty');
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
19
|
+
try {
|
|
20
|
+
const response = await this.fetchFn(this.endpoint, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({ payload }),
|
|
24
|
+
signal: controller.signal,
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error(`save upload failed with HTTP ${response.status}`);
|
|
28
|
+
return { ok: true, status: response.status };
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** fetches whatever payload the endpoint currently holds, ready for `SaveSystem.importSlot` */
|
|
35
|
+
async download() {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
38
|
+
try {
|
|
39
|
+
const response = await this.fetchFn(this.endpoint, { signal: controller.signal });
|
|
40
|
+
if (!response.ok)
|
|
41
|
+
throw new Error(`save download failed with HTTP ${response.status}`);
|
|
42
|
+
const data = (await response.json());
|
|
43
|
+
if (typeof data.payload !== 'string')
|
|
44
|
+
throw new Error('save download response missing a string payload');
|
|
45
|
+
return data.payload;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=SaveSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveSync.js","sourceRoot":"","sources":["../../src/core/SaveSync.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,cAAc;IAK1B,YAAY,OAAwB;QACnC,IAAI,CAAC,OAAO,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACjF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC3F,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,MAAM,CAAC,OAAe;QAC3B,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACnE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAClD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;gBACjC,MAAM,EAAE,UAAU,CAAC,MAAM;aACzB,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACrF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9C,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;IAED,+FAA+F;IAC/F,KAAK,CAAC,QAAQ;QACb,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;YAClF,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACvF,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA0B,CAAC;YAC9D,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACzG,OAAO,IAAI,CAAC,OAAO,CAAC;QACrB,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XOR-with-a-key text scrambling. This is explicitly **not encryption** and must never be
|
|
3
|
+
* presented to a player or a game's own documentation as one: it stops a save being
|
|
4
|
+
* hand-edited by someone opening it in a text editor, and deliberately nothing stronger -
|
|
5
|
+
* the key is recoverable from the scrambled text plus any known plaintext fragment, the
|
|
6
|
+
* classic XOR weakness, accepted on purpose rather than overlooked. Reach for this only
|
|
7
|
+
* where "not trivially editable" is the actual requirement, per `SaveSystem.exportSlot`'s
|
|
8
|
+
* own use of it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scramble(text: string, key: string): string;
|
|
11
|
+
/** reverses `scramble`; throws if `key` does not match the one it was scrambled with (garbled UTF-8 rather than a silent wrong answer) */
|
|
12
|
+
export declare function unscramble(payload: string, key: string): string;
|
|
13
|
+
//# sourceMappingURL=Scramble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scramble.d.ts","sourceRoot":"","sources":["../../src/core/Scramble.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED,0IAA0I;AAC1I,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAO/D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XOR-with-a-key text scrambling. This is explicitly **not encryption** and must never be
|
|
3
|
+
* presented to a player or a game's own documentation as one: it stops a save being
|
|
4
|
+
* hand-edited by someone opening it in a text editor, and deliberately nothing stronger -
|
|
5
|
+
* the key is recoverable from the scrambled text plus any known plaintext fragment, the
|
|
6
|
+
* classic XOR weakness, accepted on purpose rather than overlooked. Reach for this only
|
|
7
|
+
* where "not trivially editable" is the actual requirement, per `SaveSystem.exportSlot`'s
|
|
8
|
+
* own use of it.
|
|
9
|
+
*/
|
|
10
|
+
export function scramble(text, key) {
|
|
11
|
+
if (!key)
|
|
12
|
+
throw new Error('scramble needs a non-empty key');
|
|
13
|
+
const bytes = new TextEncoder().encode(text);
|
|
14
|
+
const keyBytes = new TextEncoder().encode(key);
|
|
15
|
+
const out = new Uint8Array(bytes.length);
|
|
16
|
+
for (let i = 0; i < bytes.length; i++)
|
|
17
|
+
out[i] = bytes[i] ^ keyBytes[i % keyBytes.length];
|
|
18
|
+
return toBase64(out);
|
|
19
|
+
}
|
|
20
|
+
/** reverses `scramble`; throws if `key` does not match the one it was scrambled with (garbled UTF-8 rather than a silent wrong answer) */
|
|
21
|
+
export function unscramble(payload, key) {
|
|
22
|
+
if (!key)
|
|
23
|
+
throw new Error('scramble needs a non-empty key');
|
|
24
|
+
const bytes = fromBase64(payload);
|
|
25
|
+
const keyBytes = new TextEncoder().encode(key);
|
|
26
|
+
const out = new Uint8Array(bytes.length);
|
|
27
|
+
for (let i = 0; i < bytes.length; i++)
|
|
28
|
+
out[i] = bytes[i] ^ keyBytes[i % keyBytes.length];
|
|
29
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(out);
|
|
30
|
+
}
|
|
31
|
+
function toBase64(bytes) {
|
|
32
|
+
let binary = '';
|
|
33
|
+
for (const byte of bytes)
|
|
34
|
+
binary += String.fromCharCode(byte);
|
|
35
|
+
return btoa(binary);
|
|
36
|
+
}
|
|
37
|
+
function fromBase64(text) {
|
|
38
|
+
const binary = atob(text);
|
|
39
|
+
const bytes = new Uint8Array(binary.length);
|
|
40
|
+
for (let i = 0; i < binary.length; i++)
|
|
41
|
+
bytes[i] = binary.charCodeAt(i);
|
|
42
|
+
return bytes;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=Scramble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scramble.js","sourceRoot":"","sources":["../../src/core/Scramble.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAW;IACjD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,0IAA0I;AAC1I,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,GAAW;IACtD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzF,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAiB;IAClC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type SaveStorage } from './Save.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Counts how many times a game has been launched, persisted across page loads.
|
|
4
|
+
*
|
|
5
|
+
* Not an achievement, a quest counter, or anything a game reads to change its own
|
|
6
|
+
* behaviour: the one thing this exists for is the signal a native wrapper (Capacitor,
|
|
7
|
+
* Tauri, whatever a game ships through) needs to decide whether a player has used the
|
|
8
|
+
* game enough to plausibly be worth asking for a rating. `mwg` only counts; it never
|
|
9
|
+
* prompts, and has no opinion on what "enough" means.
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionOptions {
|
|
12
|
+
/** namespaces the count, so two games sharing an origin never collide */
|
|
13
|
+
namespace?: string;
|
|
14
|
+
storage?: SaveStorage;
|
|
15
|
+
}
|
|
16
|
+
export declare class Session {
|
|
17
|
+
/** how many times this game has been launched, including this one */
|
|
18
|
+
readonly launches: number;
|
|
19
|
+
constructor(options?: SessionOptions);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=Session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../../src/core/Session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7D;;;;;;;;GAQG;AAEH,MAAM,WAAW,cAAc;IAC9B,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qBAAa,OAAO;IACnB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,OAAO,GAAE,cAAmB,EAOvC;CACD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defaultStorage } from './Save.js';
|
|
2
|
+
export class Session {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
const storage = options.storage ?? defaultStorage();
|
|
5
|
+
const key = `mwg-session:${options.namespace ?? 'default'}`;
|
|
6
|
+
const previous = Number(storage.read(key) ?? '0');
|
|
7
|
+
this.launches = (Number.isFinite(previous) ? previous : 0) + 1;
|
|
8
|
+
storage.write(key, String(this.launches));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=Session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Session.js","sourceRoot":"","sources":["../../src/core/Session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AAkB7D,MAAM,OAAO,OAAO;IAInB,YAAY,OAAO,GAAmB,EAAE;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,cAAc,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,eAAe,OAAO,CAAC,SAAS,IAAI,SAAS,EAAE,CAAC;QAE5D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3C,CAAC;CACD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One wave: `entries` spawn spread evenly across `duration` seconds, starting `delay`
|
|
3
|
+
* seconds after the spawner itself starts (not after the previous wave ends - waves may
|
|
4
|
+
* overlap, which is exactly what a game escalating toward a boss often wants).
|
|
5
|
+
*/
|
|
6
|
+
export interface Wave<T> {
|
|
7
|
+
delay: number;
|
|
8
|
+
entries: readonly {
|
|
9
|
+
kind: T;
|
|
10
|
+
count: number;
|
|
11
|
+
}[];
|
|
12
|
+
/** spread `entries` across this many seconds; 0 (the default) spawns them all at once */
|
|
13
|
+
duration?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SpawnerOptions<T> {
|
|
16
|
+
waves: readonly Wave<T>[];
|
|
17
|
+
onSpawn: (kind: T) => void;
|
|
18
|
+
/** fires once, the moment the first entry of a wave actually spawns */
|
|
19
|
+
onWaveStart?: (waveIndex: number) => void;
|
|
20
|
+
/** fires once every scheduled spawn across every wave has happened */
|
|
21
|
+
onComplete?: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A `dt`-driven timer for escalating, timed spawning - "wave 3 starts at t+45s, spawns 8 of
|
|
25
|
+
* kind A and 2 of kind B over the next 10s" - distinct in shape from `roguelike.Scheduler`,
|
|
26
|
+
* which orders whose turn it is by energy cost and has no concept of real time at all. Not
|
|
27
|
+
* specific to tower defense: any game with escalating timed spawns (a horde mode, a survival
|
|
28
|
+
* minigame) wants the same primitive.
|
|
29
|
+
*
|
|
30
|
+
* The whole schedule is flattened and sorted once, at construction, rather than tracked wave
|
|
31
|
+
* by wave - simpler to get right, and cheap enough that a spawner is not something a game
|
|
32
|
+
* expects to have thousands of.
|
|
33
|
+
*/
|
|
34
|
+
export declare class Spawner<T> {
|
|
35
|
+
private schedule;
|
|
36
|
+
private cursor;
|
|
37
|
+
private elapsed;
|
|
38
|
+
private startedWaves;
|
|
39
|
+
private completed;
|
|
40
|
+
private onSpawn;
|
|
41
|
+
private onWaveStart?;
|
|
42
|
+
private onComplete?;
|
|
43
|
+
constructor(options: SpawnerOptions<T>);
|
|
44
|
+
update(dt: number): void;
|
|
45
|
+
/** true once every scheduled spawn has happened */
|
|
46
|
+
get isComplete(): boolean;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=Spawner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spawner.d.ts","sourceRoot":"","sources":["../../src/core/Spawner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAE/C,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAE3B,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAQD;;;;;;;;;;GAUG;AACH,qBAAa,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAK;IAIpB,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,WAAW,CAAC,CAA8B;IAClD,OAAO,CAAC,UAAU,CAAC,CAAa;IAEhC,YAAY,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAuBrC;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAkBvB;IAED,mDAAmD;IACnD,IAAI,UAAU,IAAI,OAAO,CAExB;CACD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `dt`-driven timer for escalating, timed spawning - "wave 3 starts at t+45s, spawns 8 of
|
|
3
|
+
* kind A and 2 of kind B over the next 10s" - distinct in shape from `roguelike.Scheduler`,
|
|
4
|
+
* which orders whose turn it is by energy cost and has no concept of real time at all. Not
|
|
5
|
+
* specific to tower defense: any game with escalating timed spawns (a horde mode, a survival
|
|
6
|
+
* minigame) wants the same primitive.
|
|
7
|
+
*
|
|
8
|
+
* The whole schedule is flattened and sorted once, at construction, rather than tracked wave
|
|
9
|
+
* by wave - simpler to get right, and cheap enough that a spawner is not something a game
|
|
10
|
+
* expects to have thousands of.
|
|
11
|
+
*/
|
|
12
|
+
export class Spawner {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.schedule = [];
|
|
15
|
+
this.cursor = 0;
|
|
16
|
+
this.elapsed = 0;
|
|
17
|
+
//a Set, not a single "last" index - overlapping waves (see the Wave doc) interleave in
|
|
18
|
+
//the time-sorted schedule, so a wave already started can recur after a different one's
|
|
19
|
+
//entry sorts between two of its own
|
|
20
|
+
this.startedWaves = new Set();
|
|
21
|
+
this.completed = false;
|
|
22
|
+
this.onSpawn = options.onSpawn;
|
|
23
|
+
this.onWaveStart = options.onWaveStart;
|
|
24
|
+
this.onComplete = options.onComplete;
|
|
25
|
+
options.waves.forEach((wave, waveIndex) => {
|
|
26
|
+
const total = wave.entries.reduce((sum, entry) => sum + entry.count, 0);
|
|
27
|
+
const duration = wave.duration ?? 0;
|
|
28
|
+
let i = 0;
|
|
29
|
+
for (const entry of wave.entries) {
|
|
30
|
+
for (let n = 0; n < entry.count; n++, i++) {
|
|
31
|
+
const spread = total > 1 && duration > 0 ? (i / (total - 1)) * duration : 0;
|
|
32
|
+
this.schedule.push({ time: wave.delay + spread, kind: entry.kind, waveIndex });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
this.schedule.sort((a, b) => a.time - b.time);
|
|
37
|
+
if (this.schedule.length === 0) {
|
|
38
|
+
this.completed = true;
|
|
39
|
+
this.onComplete?.();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
update(dt) {
|
|
43
|
+
if (this.completed)
|
|
44
|
+
return;
|
|
45
|
+
this.elapsed += dt;
|
|
46
|
+
while (this.cursor < this.schedule.length && this.schedule[this.cursor].time <= this.elapsed) {
|
|
47
|
+
const spawn = this.schedule[this.cursor];
|
|
48
|
+
if (!this.startedWaves.has(spawn.waveIndex)) {
|
|
49
|
+
this.startedWaves.add(spawn.waveIndex);
|
|
50
|
+
this.onWaveStart?.(spawn.waveIndex);
|
|
51
|
+
}
|
|
52
|
+
this.onSpawn(spawn.kind);
|
|
53
|
+
this.cursor++;
|
|
54
|
+
}
|
|
55
|
+
if (this.cursor >= this.schedule.length) {
|
|
56
|
+
this.completed = true;
|
|
57
|
+
this.onComplete?.();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** true once every scheduled spawn has happened */
|
|
61
|
+
get isComplete() {
|
|
62
|
+
return this.completed;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=Spawner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spawner.js","sourceRoot":"","sources":["../../src/core/Spawner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one interpolation primitive every dt-driven feature wants: given a duration and a
|
|
3
|
+
* function of progress, run it from 0 to 1 and resolve. `DialogueStage`'s fades and
|
|
4
|
+
* `render.Camera`'s shake decay each used to hand-roll this same shape under a different
|
|
5
|
+
* name; this is that shape, pulled out once.
|
|
6
|
+
*/
|
|
7
|
+
export type Easing = (t: number) => number;
|
|
8
|
+
/** a small standard set, named the way CSS and most engines already do */
|
|
9
|
+
export declare const Easing: Record<'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic', Easing>;
|
|
10
|
+
/** Runs any number of concurrent tweens; a game or a widget drives it from its own `update(dt)`. */
|
|
11
|
+
export declare class Tweener {
|
|
12
|
+
private tweens;
|
|
13
|
+
/**
|
|
14
|
+
* Runs `apply` with progress eased from 0 to 1 over `duration` seconds, resolving once it
|
|
15
|
+
* reaches 1. A non-positive duration applies the end state at once rather than waiting a
|
|
16
|
+
* frame for it.
|
|
17
|
+
*/
|
|
18
|
+
tween(duration: number, apply: (t: number) => void, ease?: Easing): Promise<void>;
|
|
19
|
+
update(dt: number): void;
|
|
20
|
+
/** true while any tween is still running */
|
|
21
|
+
get isBusy(): boolean;
|
|
22
|
+
/** drops every running tween without applying its end state or resolving it - a teardown, not a completion */
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=Tween.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tween.d.ts","sourceRoot":"","sources":["../../src/core/Tween.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAE3C,0EAA0E;AAC1E,eAAO,MAAM,MAAM,EAAE,MAAM,CAC1B,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,eAAe,GAAG,aAAa,GAAG,cAAc,GAAG,gBAAgB,EAC7G,MAAM,CASN,CAAC;AAUF,oGAAoG;AACpG,qBAAa,OAAO;IACnB,OAAO,CAAC,MAAM,CAAqB;IAEnC;;;;OAIG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ/F;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAevB;IAED,4CAA4C;IAC5C,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,8GAA8G;IAC9G,KAAK,IAAI,IAAI,CAEZ;CACD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one interpolation primitive every dt-driven feature wants: given a duration and a
|
|
3
|
+
* function of progress, run it from 0 to 1 and resolve. `DialogueStage`'s fades and
|
|
4
|
+
* `render.Camera`'s shake decay each used to hand-roll this same shape under a different
|
|
5
|
+
* name; this is that shape, pulled out once.
|
|
6
|
+
*/
|
|
7
|
+
/** a small standard set, named the way CSS and most engines already do */
|
|
8
|
+
export const Easing = {
|
|
9
|
+
linear: (t) => t,
|
|
10
|
+
easeInQuad: (t) => t * t,
|
|
11
|
+
easeOutQuad: (t) => t * (2 - t),
|
|
12
|
+
easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : 1 - ((-2 * t + 2) ** 2) / 2),
|
|
13
|
+
easeInCubic: (t) => t * t * t,
|
|
14
|
+
easeOutCubic: (t) => 1 - (1 - t) ** 3,
|
|
15
|
+
easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : 1 - ((-2 * t + 2) ** 3) / 2),
|
|
16
|
+
};
|
|
17
|
+
/** Runs any number of concurrent tweens; a game or a widget drives it from its own `update(dt)`. */
|
|
18
|
+
export class Tweener {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.tweens = [];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs `apply` with progress eased from 0 to 1 over `duration` seconds, resolving once it
|
|
24
|
+
* reaches 1. A non-positive duration applies the end state at once rather than waiting a
|
|
25
|
+
* frame for it.
|
|
26
|
+
*/
|
|
27
|
+
tween(duration, apply, ease = Easing.linear) {
|
|
28
|
+
if (!(duration > 0)) {
|
|
29
|
+
apply(1);
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
this.tweens.push({ elapsed: 0, duration, apply, ease, resolve });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
update(dt) {
|
|
37
|
+
if (this.tweens.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
//iterate a copy: a tween's resolve may start another, which must not be advanced
|
|
40
|
+
//again within this same update
|
|
41
|
+
for (const tween of [...this.tweens]) {
|
|
42
|
+
tween.elapsed += dt;
|
|
43
|
+
const t = Math.min(1, tween.elapsed / tween.duration);
|
|
44
|
+
tween.apply(tween.ease(t));
|
|
45
|
+
if (t >= 1) {
|
|
46
|
+
this.tweens.splice(this.tweens.indexOf(tween), 1);
|
|
47
|
+
tween.resolve();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** true while any tween is still running */
|
|
52
|
+
get isBusy() {
|
|
53
|
+
return this.tweens.length > 0;
|
|
54
|
+
}
|
|
55
|
+
/** drops every running tween without applying its end state or resolving it - a teardown, not a completion */
|
|
56
|
+
clear() {
|
|
57
|
+
this.tweens = [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=Tween.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tween.js","sourceRoot":"","sources":["../../src/core/Tween.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,MAAM,GAGf;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;IACxB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/B,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACzE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7B,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;CAC9E,CAAC;AAUF,oGAAoG;AACpG,MAAM,OAAO,OAAO;IAApB;QACS,WAAM,GAAkB,EAAE,CAAC;IA2CpC,CAAC;IAzCA;;;;OAIG;IACH,KAAK,CAAC,QAAgB,EAAE,KAA0B,EAAE,IAAI,GAAW,MAAM,CAAC,MAAM;QAC/E,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,CAAC,CAAC,CAAC;YACT,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,EAAU;QAChB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErC,iFAAiF;QACjF,+BAA+B;QAC/B,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;YACtD,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAE3B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACZ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;gBAClD,KAAK,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC;QACF,CAAC;IACF,CAAC;IAED,4CAA4C;IAC5C,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,8GAA8G;IAC9G,KAAK;QACJ,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IAClB,CAAC;CACD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded, generic back/forward step through recent turns - a mode a game opts into
|
|
3
|
+
* (useless in a permadeath roguelike, useful elsewhere), agnostic about what "a turn's
|
|
4
|
+
* state" actually contains the same way `SaveSystem` is agnostic about what "the save"
|
|
5
|
+
* contains: a game serializes its own turn-scoped state (a `Level`, a `BoardGrid`, whatever
|
|
6
|
+
* it has) and this only ever stores and replays snapshots of it, never interprets them.
|
|
7
|
+
*
|
|
8
|
+
* Distinct from `Recorder`/`Player` (item 42), which replay a whole `Input.onAction` log
|
|
9
|
+
* deterministically for testing, not a bounded, player-facing "undo my last move".
|
|
10
|
+
*/
|
|
11
|
+
export interface UndoHistoryOptions {
|
|
12
|
+
/** states retained before the oldest is dropped; defaults to 50 */
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class UndoHistory<T> {
|
|
16
|
+
private history;
|
|
17
|
+
private cursor;
|
|
18
|
+
private readonly limit;
|
|
19
|
+
constructor(options?: UndoHistoryOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Records `state` as the new current point - the normal "a turn was taken" case. Any
|
|
22
|
+
* redo history beyond the current point is discarded, the same rule undo/redo everywhere
|
|
23
|
+
* follows: taking a new turn after undoing invalidates whatever future it undid away from.
|
|
24
|
+
*/
|
|
25
|
+
push(state: T): void;
|
|
26
|
+
get canUndo(): boolean;
|
|
27
|
+
get canRedo(): boolean;
|
|
28
|
+
/** steps back one state and returns it; null when already at the oldest retained state */
|
|
29
|
+
undo(): T | null;
|
|
30
|
+
/** steps forward one state and returns it; null when already at the newest state */
|
|
31
|
+
redo(): T | null;
|
|
32
|
+
/** the state at the current point, or null before anything has ever been pushed */
|
|
33
|
+
get current(): T | null;
|
|
34
|
+
/** drops everything - a new game, a floor transition, anywhere undoing across the boundary makes no sense */
|
|
35
|
+
clear(): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=UndoHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UndoHistory.d.ts","sourceRoot":"","sources":["../../src/core/UndoHistory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IAClC,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,YAAY,OAAO,GAAE,kBAAuB,EAE3C;IAED;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAKnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,0FAA0F;IAC1F,IAAI,IAAI,CAAC,GAAG,IAAI,CAIf;IAED,oFAAoF;IACpF,IAAI,IAAI,CAAC,GAAG,IAAI,CAIf;IAED,mFAAmF;IACnF,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,CAEtB;IAED,6GAA6G;IAC7G,KAAK,IAAI,IAAI,CAGZ;CACD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class UndoHistory {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.history = [];
|
|
4
|
+
this.cursor = -1;
|
|
5
|
+
this.limit = Math.max(1, options.limit ?? 50);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Records `state` as the new current point - the normal "a turn was taken" case. Any
|
|
9
|
+
* redo history beyond the current point is discarded, the same rule undo/redo everywhere
|
|
10
|
+
* follows: taking a new turn after undoing invalidates whatever future it undid away from.
|
|
11
|
+
*/
|
|
12
|
+
push(state) {
|
|
13
|
+
this.history.length = this.cursor + 1;
|
|
14
|
+
this.history.push(state);
|
|
15
|
+
if (this.history.length > this.limit)
|
|
16
|
+
this.history.shift();
|
|
17
|
+
this.cursor = this.history.length - 1;
|
|
18
|
+
}
|
|
19
|
+
get canUndo() {
|
|
20
|
+
return this.cursor > 0;
|
|
21
|
+
}
|
|
22
|
+
get canRedo() {
|
|
23
|
+
return this.cursor < this.history.length - 1;
|
|
24
|
+
}
|
|
25
|
+
/** steps back one state and returns it; null when already at the oldest retained state */
|
|
26
|
+
undo() {
|
|
27
|
+
if (!this.canUndo)
|
|
28
|
+
return null;
|
|
29
|
+
this.cursor--;
|
|
30
|
+
return this.history[this.cursor];
|
|
31
|
+
}
|
|
32
|
+
/** steps forward one state and returns it; null when already at the newest state */
|
|
33
|
+
redo() {
|
|
34
|
+
if (!this.canRedo)
|
|
35
|
+
return null;
|
|
36
|
+
this.cursor++;
|
|
37
|
+
return this.history[this.cursor];
|
|
38
|
+
}
|
|
39
|
+
/** the state at the current point, or null before anything has ever been pushed */
|
|
40
|
+
get current() {
|
|
41
|
+
return this.cursor >= 0 ? this.history[this.cursor] : null;
|
|
42
|
+
}
|
|
43
|
+
/** drops everything - a new game, a floor transition, anywhere undoing across the boundary makes no sense */
|
|
44
|
+
clear() {
|
|
45
|
+
this.history = [];
|
|
46
|
+
this.cursor = -1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=UndoHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UndoHistory.js","sourceRoot":"","sources":["../../src/core/UndoHistory.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,WAAW;IAKvB,YAAY,OAAO,GAAuB,EAAE;QAJpC,YAAO,GAAQ,EAAE,CAAC;QAClB,WAAM,GAAG,CAAC,CAAC,CAAC;QAInB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,KAAQ;QACZ,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC3D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,0FAA0F;IAC1F,IAAI;QACH,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,oFAAoF;IACpF,IAAI;QACH,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,mFAAmF;IACnF,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAED,6GAA6G;IAC7G,KAAK;QACJ,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClB,CAAC;CACD"}
|