@datamoc/mw_games 0.4.2 → 0.5.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 +2 -1
- package/dist/actors/ItemStatusEffect.d.ts +1 -1
- package/dist/actors/ItemStatusEffect.d.ts.map +1 -1
- package/dist/actors/ItemStatusEffect.js +2 -11
- package/dist/actors/ItemStatusEffect.js.map +1 -1
- package/dist/actors/StatusEffect.d.ts +11 -0
- package/dist/actors/StatusEffect.d.ts.map +1 -1
- package/dist/actors/StatusEffect.js +21 -8
- package/dist/actors/StatusEffect.js.map +1 -1
- package/dist/core/HttpTransport.d.ts +1 -1
- package/dist/core/HttpTransport.js +1 -1
- package/dist/core/News.d.ts +6 -13
- package/dist/core/News.d.ts.map +1 -1
- package/dist/core/News.js +10 -23
- package/dist/core/News.js.map +1 -1
- package/dist/core/Reactions.d.ts +67 -0
- package/dist/core/Reactions.d.ts.map +1 -0
- package/dist/core/Reactions.js +92 -0
- package/dist/core/Reactions.js.map +1 -0
- package/dist/core/RunHistory.d.ts +1 -2
- package/dist/core/RunHistory.d.ts.map +1 -1
- package/dist/core/RunHistory.js +5 -6
- package/dist/core/RunHistory.js.map +1 -1
- package/dist/core/Stats.d.ts +1 -2
- package/dist/core/Stats.d.ts.map +1 -1
- package/dist/core/Stats.js +5 -6
- package/dist/core/Stats.js.map +1 -1
- package/dist/core/StoredValue.d.ts +21 -0
- package/dist/core/StoredValue.d.ts.map +1 -0
- package/dist/core/StoredValue.js +28 -0
- package/dist/core/StoredValue.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/mw_games.global.js +90 -90
- package/dist/roguelike/Level.d.ts +10 -0
- package/dist/roguelike/Level.d.ts.map +1 -1
- package/dist/roguelike/Level.js +57 -17
- package/dist/roguelike/Level.js.map +1 -1
- package/dist/roguelike/Pathfinder.js +19 -19
- package/dist/roguelike/Pathfinder.js.map +1 -1
- package/dist/roguelike/Placement.d.ts +1 -1
- package/dist/roguelike/Placement.d.ts.map +1 -1
- package/dist/roguelike/Placement.js +5 -5
- package/dist/roguelike/Placement.js.map +1 -1
- package/dist/rpg/FreeMover.d.ts +1 -1
- package/dist/rpg/FreeMover.d.ts.map +1 -1
- package/dist/rpg/FreeMover.js +3 -6
- package/dist/rpg/FreeMover.js.map +1 -1
- package/dist/rpg/GridMover.d.ts +1 -1
- package/dist/rpg/GridMover.d.ts.map +1 -1
- package/dist/rpg/GridMover.js +3 -6
- package/dist/rpg/GridMover.js.map +1 -1
- package/dist/rpg/MovableSprite.d.ts +6 -0
- package/dist/rpg/MovableSprite.d.ts.map +1 -1
- package/dist/rpg/MovableSprite.js +9 -1
- package/dist/rpg/MovableSprite.js.map +1 -1
- package/dist/two-d/ui/BitmapLabel.d.ts +3 -9
- package/dist/two-d/ui/BitmapLabel.d.ts.map +1 -1
- package/dist/two-d/ui/BitmapLabel.js +3 -2
- package/dist/two-d/ui/BitmapLabel.js.map +1 -1
- package/dist/two-d/ui/IconGrid.d.ts +2 -3
- package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
- package/dist/two-d/ui/IconGrid.js +34 -50
- package/dist/two-d/ui/IconGrid.js.map +1 -1
- package/dist/two-d/ui/Label.d.ts +2 -8
- package/dist/two-d/ui/Label.d.ts.map +1 -1
- package/dist/two-d/ui/Label.js +4 -3
- package/dist/two-d/ui/Label.js.map +1 -1
- package/dist/two-d/ui/ListView.d.ts +3 -4
- package/dist/two-d/ui/ListView.d.ts.map +1 -1
- package/dist/two-d/ui/ListView.js +25 -43
- package/dist/two-d/ui/ListView.js.map +1 -1
- package/dist/two-d/ui/SelectionModel.d.ts +50 -0
- package/dist/two-d/ui/SelectionModel.d.ts.map +1 -0
- package/dist/two-d/ui/SelectionModel.js +73 -0
- package/dist/two-d/ui/SelectionModel.js.map +1 -0
- package/dist/two-d/ui/themedText.d.ts +20 -0
- package/dist/two-d/ui/themedText.d.ts.map +1 -0
- package/dist/two-d/ui/themedText.js +9 -0
- package/dist/two-d/ui/themedText.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
- package/tools/compile-resources.mjs +1 -1
- package/tools/compress-dist.d.mts +16 -0
- package/tools/compress-dist.mjs +116 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ of their own; improvements to `mwg`'s own files are shared back.
|
|
|
11
11
|
**[Live examples and API docs](https://datamoc.github.io/mwg/)**: every example below,
|
|
12
12
|
playable in the browser with no download, plus the generated API reference.
|
|
13
13
|
|
|
14
|
-
> **Status: early release (v0.4.
|
|
14
|
+
> **Status: early release (v0.4.3).** Every module in the shared floor below, plus optional
|
|
15
15
|
> 3D, mobile (Capacitor) and desktop (WebView2) packaging, is built and tested - see
|
|
16
16
|
> [ROADMAP.md](ROADMAP.md) for the full, numbered history.
|
|
17
17
|
>
|
|
@@ -135,6 +135,7 @@ only some do.
|
|
|
135
135
|
| | signals and listeners | `mwg/core` |
|
|
136
136
|
| | deterministic seeded RNG, reproducible across machines | `mwg/core` |
|
|
137
137
|
| | named, versioned save slots with a preview | `mwg/core` |
|
|
138
|
+
| | declarative reactions to any state crossing a condition, in place of an `if` cascade | `mwg/core` |
|
|
138
139
|
| **render** | sprite batching at thousands of tiles | PixiJS |
|
|
139
140
|
| | per-sprite colour transform: multiply **and** add | `mwg/two-d/render` |
|
|
140
141
|
| | animated sprites driven by named frame sets | `mwg/two-d/render` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EffectClock } from './StatusEffect.ts';
|
|
2
2
|
export interface ItemStatusEffectOptions<T> {
|
|
3
3
|
/** fields applied to the item for the duration, restored to their prior value on expiry */
|
|
4
4
|
fields: Partial<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ItemStatusEffect.d.ts","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ItemStatusEffect.d.ts","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,mBAAmB,CAAC;AAEjE,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACzC,2FAA2F;IAC3F,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,qFAAqF;IACrF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,sGAAsG;AACtG,MAAM,WAAW,sBAAsB;IACtC,MAAM,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACrD,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,sBAAsB,CAYxB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { leaseClock } from './StatusEffect.js';
|
|
1
2
|
/**
|
|
2
3
|
* A temporary status on an item itself - a weapon coated in poison for a fixed number of
|
|
3
4
|
* turns, a shield reinforced until it wears off, a ring blessed only "for this floor" - the
|
|
@@ -33,16 +34,6 @@ export function applyItemStatusEffect(item, clock, options) {
|
|
|
33
34
|
for (const [key, value] of previous)
|
|
34
35
|
item[key] = value;
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
-
duration: options.duration,
|
|
38
|
-
tick: (turn) => options.tick?.(turn),
|
|
39
|
-
onExpire: restore,
|
|
40
|
-
});
|
|
41
|
-
return {
|
|
42
|
-
cancel: () => {
|
|
43
|
-
clock.remove(id);
|
|
44
|
-
restore();
|
|
45
|
-
},
|
|
46
|
-
};
|
|
37
|
+
return leaseClock(clock, { duration: options.duration, tick: options.tick, onExpire: restore });
|
|
47
38
|
}
|
|
48
39
|
//# sourceMappingURL=ItemStatusEffect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ItemStatusEffect.js","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ItemStatusEffect.js","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAkBjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,qBAAqB,CACpC,IAAO,EACP,KAAkB,EAClB,OAAmC;IAEnC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAgB,EAAE,CAAC;QAC9D,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAe,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,GAAS,EAAE;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,QAAQ;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxD,CAAC,CAAC;IAEF,OAAO,UAAU,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AACjG,CAAC"}
|
|
@@ -24,6 +24,17 @@ export interface StatusEffectOptions {
|
|
|
24
24
|
export interface StatusEffectHandle {
|
|
25
25
|
cancel(): void;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The half both `applyStatusEffect` and `applyItemStatusEffect` share: register `onExpire`
|
|
29
|
+
* with `clock` for `duration` turns, and hand back a `cancel()` that unregisters early and
|
|
30
|
+
* still runs `onExpire` once. Lives here rather than in a third file because this module
|
|
31
|
+
* already owns `EffectClock`, the only type it needs.
|
|
32
|
+
*/
|
|
33
|
+
export declare function leaseClock(clock: EffectClock, options: {
|
|
34
|
+
duration: number;
|
|
35
|
+
tick?: (turn: number) => void;
|
|
36
|
+
onExpire: () => void;
|
|
37
|
+
}): StatusEffectHandle;
|
|
27
38
|
/**
|
|
28
39
|
* A temporary buff or debuff: applies `modifiers` to `stats` and registers their expiry with
|
|
29
40
|
* `clock`, so a game never has to remember to remove what it added. The seam `TurnClock` and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusEffect.d.ts","sourceRoot":"","sources":["../../src/actors/StatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,MAAM,CAAC;IAChG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IACnC,8FAA8F;IAC9F,SAAS,EAAE,SAAS,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;IAE/C,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IAEjB,oFAAoF;IACpF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IAClC,MAAM,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,mBAAmB,GAC1B,kBAAkB,
|
|
1
|
+
{"version":3,"file":"StatusEffect.d.ts","sourceRoot":"","sources":["../../src/actors/StatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,MAAM,CAAC;IAChG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IACnC,8FAA8F;IAC9F,SAAS,EAAE,SAAS,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;IAE/C,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IAEjB,oFAAoF;IACpF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IAClC,MAAM,IAAI,IAAI,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAAE,GAChF,kBAAkB,CAapB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,mBAAmB,GAC1B,kBAAkB,CASpB"}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import { StatBlock } from './StatBlock.js';
|
|
2
|
+
/**
|
|
3
|
+
* The half both `applyStatusEffect` and `applyItemStatusEffect` share: register `onExpire`
|
|
4
|
+
* with `clock` for `duration` turns, and hand back a `cancel()` that unregisters early and
|
|
5
|
+
* still runs `onExpire` once. Lives here rather than in a third file because this module
|
|
6
|
+
* already owns `EffectClock`, the only type it needs.
|
|
7
|
+
*/
|
|
8
|
+
export function leaseClock(clock, options) {
|
|
9
|
+
const id = clock.add({
|
|
10
|
+
duration: options.duration,
|
|
11
|
+
tick: (turn) => options.tick?.(turn),
|
|
12
|
+
onExpire: options.onExpire,
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
cancel: () => {
|
|
16
|
+
clock.remove(id);
|
|
17
|
+
options.onExpire();
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
2
21
|
/**
|
|
3
22
|
* A temporary buff or debuff: applies `modifiers` to `stats` and registers their expiry with
|
|
4
23
|
* `clock`, so a game never has to remember to remove what it added. The seam `TurnClock` and
|
|
@@ -31,16 +50,10 @@ export function applyStatusEffect(stats, clock, options) {
|
|
|
31
50
|
const source = Symbol('statusEffect');
|
|
32
51
|
for (const modifier of options.modifiers)
|
|
33
52
|
stats.addModifier({ ...modifier, source });
|
|
34
|
-
|
|
53
|
+
return leaseClock(clock, {
|
|
35
54
|
duration: options.duration,
|
|
36
|
-
tick:
|
|
55
|
+
tick: options.tick,
|
|
37
56
|
onExpire: () => stats.removeModifiersFrom(source),
|
|
38
57
|
});
|
|
39
|
-
return {
|
|
40
|
-
cancel: () => {
|
|
41
|
-
clock.remove(id);
|
|
42
|
-
stats.removeModifiersFrom(source);
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
58
|
}
|
|
46
59
|
//# sourceMappingURL=StatusEffect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusEffect.js","sourceRoot":"","sources":["../../src/actors/StatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAC;AA4B1D
|
|
1
|
+
{"version":3,"file":"StatusEffect.js","sourceRoot":"","sources":["../../src/actors/StatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAC;AA4B1D;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACzB,KAAkB,EAClB,OAAkF;IAElF,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;QACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC1B,CAAC,CAAC;IAEH,OAAO;QACN,MAAM,EAAE,GAAG,EAAE;YACZ,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACjB,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpB,CAAC;KACD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,iBAAiB,CAChC,KAAgB,EAChB,KAAkB,EAClB,OAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IACtC,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS;QAAE,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAErF,OAAO,UAAU,CAAC,KAAK,EAAE;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC;KACjD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -5,7 +5,7 @@ export interface HttpTransportOptions {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* The constructor validation and timeout-wrapped fetch shared by every injectable HTTPS
|
|
8
|
-
* transport here (`FeedbackClient`, `SaveSyncClient`, `TelemetryClient`) - each one is a
|
|
8
|
+
* transport here (`FeedbackClient`, `NewsClient`, `SaveSyncClient`, `TelemetryClient`) - each one is a
|
|
9
9
|
* plain request/response against a game's own endpoint, differing only in what they send and
|
|
10
10
|
* expect back, not in how the endpoint/timeout/fetch options are validated or how a request
|
|
11
11
|
* is aborted after `timeoutMs`. `label` names the concrete client in its own error messages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The constructor validation and timeout-wrapped fetch shared by every injectable HTTPS
|
|
3
|
-
* transport here (`FeedbackClient`, `SaveSyncClient`, `TelemetryClient`) - each one is a
|
|
3
|
+
* transport here (`FeedbackClient`, `NewsClient`, `SaveSyncClient`, `TelemetryClient`) - each one is a
|
|
4
4
|
* plain request/response against a game's own endpoint, differing only in what they send and
|
|
5
5
|
* expect back, not in how the endpoint/timeout/fetch options are validated or how a request
|
|
6
6
|
* is aborted after `timeoutMs`. `label` names the concrete client in its own error messages
|
package/dist/core/News.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type SaveStorage } from './Save.ts';
|
|
2
|
+
import { HttpTransport, type HttpTransportOptions } from './HttpTransport.ts';
|
|
2
3
|
/**
|
|
3
4
|
* Fetches a game's own news/announcements feed - the inbound counterpart to
|
|
4
|
-
* `FeedbackClient`,
|
|
5
|
-
* endpoint, no backend shipped or assumed)
|
|
5
|
+
* `FeedbackClient`, built on the shared `HttpTransport` like every other client here
|
|
6
|
+
* (a game's own HTTPS endpoint, no backend shipped or assumed).
|
|
6
7
|
*
|
|
7
8
|
* @example
|
|
8
9
|
* ```ts
|
|
@@ -22,15 +23,8 @@ export interface NewsItem {
|
|
|
22
23
|
body: string;
|
|
23
24
|
publishedAt?: number;
|
|
24
25
|
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
timeoutMs?: number;
|
|
28
|
-
fetch?: typeof globalThis.fetch;
|
|
29
|
-
}
|
|
30
|
-
export declare class NewsClient {
|
|
31
|
-
private readonly endpoint;
|
|
32
|
-
private readonly timeoutMs;
|
|
33
|
-
private readonly fetchFn;
|
|
26
|
+
export type NewsOptions = HttpTransportOptions;
|
|
27
|
+
export declare class NewsClient extends HttpTransport {
|
|
34
28
|
constructor(options: NewsOptions);
|
|
35
29
|
/**
|
|
36
30
|
* Fetches and validates the feed; a malformed item throws rather than reaching a game as
|
|
@@ -46,8 +40,7 @@ export interface NewsSeenOptions {
|
|
|
46
40
|
}
|
|
47
41
|
/** which news items a player has already dismissed, so a returning player is not shown the same announcement forever */
|
|
48
42
|
export declare class NewsSeenTracker {
|
|
49
|
-
private readonly
|
|
50
|
-
private readonly key;
|
|
43
|
+
private readonly store;
|
|
51
44
|
constructor(options: NewsSeenOptions);
|
|
52
45
|
private readSeen;
|
|
53
46
|
isSeen(id: string): boolean;
|
package/dist/core/News.d.ts.map
CHANGED
|
@@ -1 +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;;;;;;;;;;;;;;;;GAgBG;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,
|
|
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,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE9E;;;;;;;;;;;;;;;;GAgBG;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,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAE/C,qBAAa,UAAW,SAAQ,aAAa;IAC5C,YAAY,OAAO,EAAE,WAAW,EAE/B;IAED;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAQtC;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,KAAK,CAAwB;IAE9C,YAAY,OAAO,EAAE,eAAe,EAKnC;IAED,OAAO,CAAC,QAAQ;IAIhB,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"}
|
package/dist/core/News.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { defaultStorage } from './Save.js';
|
|
2
2
|
import { sanitizeInboundText } from './Sanitize.js';
|
|
3
|
-
|
|
3
|
+
import { StoredValue } from './StoredValue.js';
|
|
4
|
+
import { HttpTransport } from './HttpTransport.js';
|
|
5
|
+
export class NewsClient extends HttpTransport {
|
|
4
6
|
constructor(options) {
|
|
5
|
-
|
|
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');
|
|
7
|
+
super(options, 'news');
|
|
14
8
|
}
|
|
15
9
|
/**
|
|
16
10
|
* Fetches and validates the feed; a malformed item throws rather than reaching a game as
|
|
@@ -19,20 +13,15 @@ export class NewsClient {
|
|
|
19
13
|
* `normalizeItem`'s own per-field shape check below.
|
|
20
14
|
*/
|
|
21
15
|
async fetchItems() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const response = await this.fetchFn(this.endpoint, { signal: controller.signal });
|
|
16
|
+
return this.withTimeout(async (signal) => {
|
|
17
|
+
const response = await this.fetchFn(this.endpoint, { signal });
|
|
26
18
|
if (!response.ok)
|
|
27
19
|
throw new Error(`news request failed with HTTP ${response.status}`);
|
|
28
20
|
const data = JSON.parse(sanitizeInboundText(await response.text()));
|
|
29
21
|
if (!Array.isArray(data))
|
|
30
22
|
throw new Error('news response was not an array');
|
|
31
23
|
return data.map((raw, index) => normalizeItem(raw, index));
|
|
32
|
-
}
|
|
33
|
-
finally {
|
|
34
|
-
clearTimeout(timer);
|
|
35
|
-
}
|
|
24
|
+
});
|
|
36
25
|
}
|
|
37
26
|
}
|
|
38
27
|
function normalizeItem(raw, index) {
|
|
@@ -55,12 +44,10 @@ function normalizeItem(raw, index) {
|
|
|
55
44
|
/** which news items a player has already dismissed, so a returning player is not shown the same announcement forever */
|
|
56
45
|
export class NewsSeenTracker {
|
|
57
46
|
constructor(options) {
|
|
58
|
-
this.
|
|
59
|
-
this.key = `mwg-news-seen:${options.namespace}`;
|
|
47
|
+
this.store = new StoredValue(options.storage ?? defaultStorage(), `mwg-news-seen:${options.namespace}`);
|
|
60
48
|
}
|
|
61
49
|
readSeen() {
|
|
62
|
-
|
|
63
|
-
return new Set(raw ? JSON.parse(raw) : []);
|
|
50
|
+
return new Set(this.store.read([]));
|
|
64
51
|
}
|
|
65
52
|
isSeen(id) {
|
|
66
53
|
return this.readSeen().has(id);
|
|
@@ -68,7 +55,7 @@ export class NewsSeenTracker {
|
|
|
68
55
|
markSeen(id) {
|
|
69
56
|
const seen = this.readSeen();
|
|
70
57
|
seen.add(id);
|
|
71
|
-
this.
|
|
58
|
+
this.store.write([...seen]);
|
|
72
59
|
}
|
|
73
60
|
/** `items` filtered down to whichever have not been marked seen yet */
|
|
74
61
|
unseen(items) {
|
package/dist/core/News.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"News.js","sourceRoot":"","sources":["../../src/core/News.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"News.js","sourceRoot":"","sources":["../../src/core/News.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom actions/reactions to changes in any object's state - HP dropping past a
|
|
3
|
+
* threshold, a durability hitting zero, a quest flag flipping true - watched declaratively
|
|
4
|
+
* instead of as a branch cascade of `if` checks scattered through game code.
|
|
5
|
+
*
|
|
6
|
+
* A `ReactionRule` pairs a `when` predicate over some state with an `action` the framework
|
|
7
|
+
* calls the moment that predicate turns true; `check()` runs every rule against a fresh
|
|
8
|
+
* reading, edge-triggered so a condition that stays true never re-fires on every call.
|
|
9
|
+
* Rules make no assumption about what shape they watch - an actor's `StatBlock` values, a
|
|
10
|
+
* plain object's own fields (an item's durability, a door's open flag), whatever the game
|
|
11
|
+
* passes to `check`, so the same table works for a character or an inanimate object alike.
|
|
12
|
+
* A `once: true` rule (a bond breaking, a boss entering phase two) fires a single time ever
|
|
13
|
+
* and then retires; the default is edge-triggered, firing again each time the condition
|
|
14
|
+
* leaves and returns, the shape a recurring low-health warning needs.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { ReactionTable } from '@datamoc/mw_games/core';
|
|
19
|
+
*
|
|
20
|
+
* const reactions = new ReactionTable<{ hp: number; maxHp: number }>([
|
|
21
|
+
* { id: 'critical', when: (s) => s.hp / s.maxHp <= 0.25, action: () => console.log('low hp!') },
|
|
22
|
+
* { id: 'shattered', when: (s) => s.hp <= 0, action: () => console.log('broke'), once: true },
|
|
23
|
+
* ]);
|
|
24
|
+
*
|
|
25
|
+
* reactions.check({ hp: 40, maxHp: 100 }); // ['critical'] - fires once, not every call after
|
|
26
|
+
* reactions.check({ hp: 38, maxHp: 100 }); // [] - still true, already fired
|
|
27
|
+
* reactions.check({ hp: 90, maxHp: 100 }); // [] - condition left; 'critical' may fire again later
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export interface ReactionRule<TState> {
|
|
31
|
+
id: string;
|
|
32
|
+
when: (state: Readonly<TState>) => boolean;
|
|
33
|
+
action: (state: Readonly<TState>) => void;
|
|
34
|
+
/** fires once ever, then is never checked again - a boss entering phase two, a bond broken */
|
|
35
|
+
once?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare class ReactionTable<TState> {
|
|
38
|
+
private rules;
|
|
39
|
+
private active;
|
|
40
|
+
private spent;
|
|
41
|
+
constructor(rules?: ReactionRule<TState>[]);
|
|
42
|
+
add(rule: ReactionRule<TState>): void;
|
|
43
|
+
/** drops a rule and forgets it was ever active, so a rule id can be reused later */
|
|
44
|
+
remove(id: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Evaluates every rule against `state`, firing whichever just turned true.
|
|
47
|
+
*
|
|
48
|
+
* @returns the ids that fired on exactly this call, in rule order - empty when nothing
|
|
49
|
+
* newly crossed, so a game can react ("play this sound", "show this toast") to each
|
|
50
|
+
* rule exactly once per crossing rather than re-deriving what changed itself
|
|
51
|
+
*/
|
|
52
|
+
check(state: Readonly<TState>): string[];
|
|
53
|
+
/** whether a rule fired and has not since left its condition (an edge-triggered rule) */
|
|
54
|
+
isActive(id: string): boolean;
|
|
55
|
+
/** clears every active/spent rule, as if `check` had never been called */
|
|
56
|
+
reset(): void;
|
|
57
|
+
toJSON(): {
|
|
58
|
+
active: string[];
|
|
59
|
+
spent: string[];
|
|
60
|
+
};
|
|
61
|
+
/** rebuilds a table from save data - rules are supplied fresh, the same as `Achievements` */
|
|
62
|
+
static fromJSON<TState>(rules: ReactionRule<TState>[], data: {
|
|
63
|
+
active: string[];
|
|
64
|
+
spent: string[];
|
|
65
|
+
}): ReactionTable<TState>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=Reactions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reactions.d.ts","sourceRoot":"","sources":["../../src/core/Reactions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,YAAY,CAAC,MAAM;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAC3C,MAAM,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IAE1C,8FAA8F;IAC9F,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,aAAa,CAAC,MAAM;IAChC,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAElC,YAAY,KAAK,GAAE,YAAY,CAAC,MAAM,CAAC,EAAO,EAE7C;IAED,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,CAEpC;IAED,oFAAoF;IACpF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAGvB;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAgBvC;IAED,yFAAyF;IACzF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5B;IAED,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAGZ;IAED,MAAM,IAAI;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAE9C;IAED,6FAA6F;IAC7F,MAAM,CAAC,QAAQ,CAAC,MAAM,EACrB,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,EAC7B,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,GACzC,aAAa,CAAC,MAAM,CAAC,CAKvB;CACD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom actions/reactions to changes in any object's state - HP dropping past a
|
|
3
|
+
* threshold, a durability hitting zero, a quest flag flipping true - watched declaratively
|
|
4
|
+
* instead of as a branch cascade of `if` checks scattered through game code.
|
|
5
|
+
*
|
|
6
|
+
* A `ReactionRule` pairs a `when` predicate over some state with an `action` the framework
|
|
7
|
+
* calls the moment that predicate turns true; `check()` runs every rule against a fresh
|
|
8
|
+
* reading, edge-triggered so a condition that stays true never re-fires on every call.
|
|
9
|
+
* Rules make no assumption about what shape they watch - an actor's `StatBlock` values, a
|
|
10
|
+
* plain object's own fields (an item's durability, a door's open flag), whatever the game
|
|
11
|
+
* passes to `check`, so the same table works for a character or an inanimate object alike.
|
|
12
|
+
* A `once: true` rule (a bond breaking, a boss entering phase two) fires a single time ever
|
|
13
|
+
* and then retires; the default is edge-triggered, firing again each time the condition
|
|
14
|
+
* leaves and returns, the shape a recurring low-health warning needs.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { ReactionTable } from '@datamoc/mw_games/core';
|
|
19
|
+
*
|
|
20
|
+
* const reactions = new ReactionTable<{ hp: number; maxHp: number }>([
|
|
21
|
+
* { id: 'critical', when: (s) => s.hp / s.maxHp <= 0.25, action: () => console.log('low hp!') },
|
|
22
|
+
* { id: 'shattered', when: (s) => s.hp <= 0, action: () => console.log('broke'), once: true },
|
|
23
|
+
* ]);
|
|
24
|
+
*
|
|
25
|
+
* reactions.check({ hp: 40, maxHp: 100 }); // ['critical'] - fires once, not every call after
|
|
26
|
+
* reactions.check({ hp: 38, maxHp: 100 }); // [] - still true, already fired
|
|
27
|
+
* reactions.check({ hp: 90, maxHp: 100 }); // [] - condition left; 'critical' may fire again later
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class ReactionTable {
|
|
31
|
+
constructor(rules = []) {
|
|
32
|
+
this.active = new Set();
|
|
33
|
+
this.spent = new Set();
|
|
34
|
+
this.rules = [...rules];
|
|
35
|
+
}
|
|
36
|
+
add(rule) {
|
|
37
|
+
this.rules.push(rule);
|
|
38
|
+
}
|
|
39
|
+
/** drops a rule and forgets it was ever active, so a rule id can be reused later */
|
|
40
|
+
remove(id) {
|
|
41
|
+
this.rules = this.rules.filter((rule) => rule.id !== id);
|
|
42
|
+
this.active.delete(id);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Evaluates every rule against `state`, firing whichever just turned true.
|
|
46
|
+
*
|
|
47
|
+
* @returns the ids that fired on exactly this call, in rule order - empty when nothing
|
|
48
|
+
* newly crossed, so a game can react ("play this sound", "show this toast") to each
|
|
49
|
+
* rule exactly once per crossing rather than re-deriving what changed itself
|
|
50
|
+
*/
|
|
51
|
+
check(state) {
|
|
52
|
+
const firedNow = [];
|
|
53
|
+
for (const rule of this.rules) {
|
|
54
|
+
if (rule.once && this.spent.has(rule.id))
|
|
55
|
+
continue;
|
|
56
|
+
const isTrue = rule.when(state);
|
|
57
|
+
if (isTrue && !this.active.has(rule.id)) {
|
|
58
|
+
rule.action(state);
|
|
59
|
+
firedNow.push(rule.id);
|
|
60
|
+
if (rule.once)
|
|
61
|
+
this.spent.add(rule.id);
|
|
62
|
+
}
|
|
63
|
+
if (isTrue)
|
|
64
|
+
this.active.add(rule.id);
|
|
65
|
+
else
|
|
66
|
+
this.active.delete(rule.id);
|
|
67
|
+
}
|
|
68
|
+
return firedNow;
|
|
69
|
+
}
|
|
70
|
+
/** whether a rule fired and has not since left its condition (an edge-triggered rule) */
|
|
71
|
+
isActive(id) {
|
|
72
|
+
return this.active.has(id);
|
|
73
|
+
}
|
|
74
|
+
/** clears every active/spent rule, as if `check` had never been called */
|
|
75
|
+
reset() {
|
|
76
|
+
this.active.clear();
|
|
77
|
+
this.spent.clear();
|
|
78
|
+
}
|
|
79
|
+
toJSON() {
|
|
80
|
+
return { active: [...this.active], spent: [...this.spent] };
|
|
81
|
+
}
|
|
82
|
+
/** rebuilds a table from save data - rules are supplied fresh, the same as `Achievements` */
|
|
83
|
+
static fromJSON(rules, data) {
|
|
84
|
+
const table = new ReactionTable(rules);
|
|
85
|
+
for (const id of data.active)
|
|
86
|
+
table.active.add(id);
|
|
87
|
+
for (const id of data.spent)
|
|
88
|
+
table.spent.add(id);
|
|
89
|
+
return table;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=Reactions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reactions.js","sourceRoot":"","sources":["../../src/core/Reactions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAWH,MAAM,OAAO,aAAa;IAKzB,YAAY,KAAK,GAA2B,EAAE;QAHtC,WAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QAC3B,UAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAGjC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,GAAG,CAAC,IAA0B;QAC7B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAED,oFAAoF;IACpF,MAAM,CAAC,EAAU;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAuB;QAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAS;YAEnD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACvB,IAAI,IAAI,CAAC,IAAI;oBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACxC,CAAC;YAED,IAAI,MAAM;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;gBAChC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,yFAAyF;IACzF,QAAQ,CAAC,EAAU;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,0EAA0E;IAC1E,KAAK;QACJ,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAED,MAAM;QACL,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,6FAA6F;IAC7F,MAAM,CAAC,QAAQ,CACd,KAA6B,EAC7B,IAA2C;QAE3C,MAAM,KAAK,GAAG,IAAI,aAAa,CAAS,KAAK,CAAC,CAAC;QAC/C,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjD,OAAO,KAAK,CAAC;IACd,CAAC;CACD"}
|
|
@@ -34,9 +34,8 @@ export interface RunHistoryOptions {
|
|
|
34
34
|
limit?: number;
|
|
35
35
|
}
|
|
36
36
|
export declare class RunHistory<T> {
|
|
37
|
-
private readonly
|
|
37
|
+
private readonly store;
|
|
38
38
|
private readonly limit?;
|
|
39
|
-
private readonly key;
|
|
40
39
|
constructor(options: RunHistoryOptions);
|
|
41
40
|
private readAll;
|
|
42
41
|
/** appends a completed run's summary, oldest first; drops the oldest entry once `limit` is exceeded */
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"RunHistory.d.ts","sourceRoot":"","sources":["../../src/core/RunHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;GAqBG;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,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;IAEhC,YAAY,OAAO,EAAE,iBAAiB,EAMrC;IAED,OAAO,CAAC,OAAO;IAIf,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"}
|
package/dist/core/RunHistory.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { defaultStorage } from './Save.js';
|
|
2
|
+
import { StoredValue } from './StoredValue.js';
|
|
2
3
|
export class RunHistory {
|
|
3
4
|
constructor(options) {
|
|
4
|
-
this.
|
|
5
|
+
this.store = new StoredValue(options.storage ?? defaultStorage(), `mwg-runs:${options.namespace}`);
|
|
5
6
|
this.limit = options.limit;
|
|
6
|
-
this.key = `mwg-runs:${options.namespace}`;
|
|
7
7
|
}
|
|
8
8
|
readAll() {
|
|
9
|
-
|
|
10
|
-
return raw ? JSON.parse(raw) : [];
|
|
9
|
+
return this.store.read([]);
|
|
11
10
|
}
|
|
12
11
|
/** appends a completed run's summary, oldest first; drops the oldest entry once `limit` is exceeded */
|
|
13
12
|
record(summary) {
|
|
@@ -16,7 +15,7 @@ export class RunHistory {
|
|
|
16
15
|
entries.push(entry);
|
|
17
16
|
if (this.limit !== undefined && entries.length > this.limit)
|
|
18
17
|
entries.splice(0, entries.length - this.limit);
|
|
19
|
-
this.
|
|
18
|
+
this.store.write(entries);
|
|
20
19
|
return entry;
|
|
21
20
|
}
|
|
22
21
|
/** every recorded run, oldest first */
|
|
@@ -35,7 +34,7 @@ export class RunHistory {
|
|
|
35
34
|
}
|
|
36
35
|
/** drops every recorded run */
|
|
37
36
|
clear() {
|
|
38
|
-
this.
|
|
37
|
+
this.store.remove();
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
//# sourceMappingURL=RunHistory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RunHistory.js","sourceRoot":"","sources":["../../src/core/RunHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"RunHistory.js","sourceRoot":"","sources":["../../src/core/RunHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsC/C,MAAM,OAAO,UAAU;IAItB,YAAY,OAA0B;QACrC,IAAI,CAAC,KAAK,GAAG,IAAI,WAAW,CAC3B,OAAO,CAAC,OAAO,IAAI,cAAc,EAAE,EACnC,YAAY,OAAO,CAAC,SAAS,EAAE,CAC/B,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,CAAC;IAEO,OAAO;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5B,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,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1B,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,KAAK,CAAC,MAAM,EAAE,CAAC;IACrB,CAAC;CACD"}
|
package/dist/core/Stats.d.ts
CHANGED
|
@@ -32,8 +32,7 @@ export interface PlayerStatsOptions<T, S = T> {
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export declare class PlayerStats<T, S = T> {
|
|
35
|
-
private readonly
|
|
36
|
-
private readonly key;
|
|
35
|
+
private readonly store;
|
|
37
36
|
private readonly initial;
|
|
38
37
|
private readonly combine;
|
|
39
38
|
constructor(options: PlayerStatsOptions<T, S>);
|
package/dist/core/Stats.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/core/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/core/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7D,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC;IAC3C,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,uDAAuD;IACvD,OAAO,EAAE,CAAC,CAAC;IACX,yDAAyD;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAI;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAEtD,YAAY,OAAO,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAI5C;IAED,6EAA6E;IAC7E,GAAG,IAAI,CAAC,CAEP;IAED,sFAAsF;IACtF,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAIpB;IAED,6EAA6E;IAC7E,KAAK,IAAI,IAAI,CAEZ;CACD"}
|
package/dist/core/Stats.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defaultStorage } from './Save.js';
|
|
2
|
+
import { StoredValue } from './StoredValue.js';
|
|
2
3
|
/**
|
|
3
4
|
* A lifetime running total, folded in one run at a time - distinct from `RunHistory`, which
|
|
4
5
|
* keeps individual runs and may drop the oldest ones once its own `limit` is reached.
|
|
@@ -24,25 +25,23 @@ import { defaultStorage } from './Save.js';
|
|
|
24
25
|
*/
|
|
25
26
|
export class PlayerStats {
|
|
26
27
|
constructor(options) {
|
|
27
|
-
this.
|
|
28
|
-
this.key = `mwg-stats:${options.namespace}`;
|
|
28
|
+
this.store = new StoredValue(options.storage ?? defaultStorage(), `mwg-stats:${options.namespace}`);
|
|
29
29
|
this.initial = options.initial;
|
|
30
30
|
this.combine = options.combine;
|
|
31
31
|
}
|
|
32
32
|
/** the current lifetime total, `initial` if nothing has been recorded yet */
|
|
33
33
|
get() {
|
|
34
|
-
|
|
35
|
-
return raw ? JSON.parse(raw) : this.initial;
|
|
34
|
+
return this.store.read(this.initial);
|
|
36
35
|
}
|
|
37
36
|
/** folds `summary` into the running total and persists it, returning the new total */
|
|
38
37
|
record(summary) {
|
|
39
38
|
const next = this.combine(this.get(), summary);
|
|
40
|
-
this.
|
|
39
|
+
this.store.write(next);
|
|
41
40
|
return next;
|
|
42
41
|
}
|
|
43
42
|
/** back to `initial`, as a new save file or a "reset stats" option should */
|
|
44
43
|
reset() {
|
|
45
|
-
this.
|
|
44
|
+
this.store.remove();
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
//# sourceMappingURL=Stats.js.map
|
package/dist/core/Stats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stats.js","sourceRoot":"","sources":["../../src/core/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"Stats.js","sourceRoot":"","sources":["../../src/core/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAY/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,WAAW;IAKvB,YAAY,OAAiC;QAC5C,IAAI,CAAC,KAAK,GAAG,IAAI,WAAW,CAAI,OAAO,CAAC,OAAO,IAAI,cAAc,EAAE,EAAE,aAAa,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QACvG,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,CAAC;IAED,6EAA6E;IAC7E,GAAG;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,sFAAsF;IACtF,MAAM,CAAC,OAAU;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,6EAA6E;IAC7E,KAAK;QACJ,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IACrB,CAAC;CACD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SaveStorage } from './Save.ts';
|
|
2
|
+
/**
|
|
3
|
+
* One JSON value under one storage key: the read-with-fallback / write / remove trio that
|
|
4
|
+
* `PlayerStats`, `RunHistory` and `NewsSeenTracker` each hand-rolled, with slightly
|
|
5
|
+
* different falsy-vs-null edges per copy. The one here treats any missing or empty read as
|
|
6
|
+
* absent (the `raw ? ... : fallback` rule two of the three already followed); corrupt JSON
|
|
7
|
+
* still throws, the same as before, since silently inventing a value would hide a real bug.
|
|
8
|
+
*
|
|
9
|
+
* Internal: multi-key shapes (`Collection`'s prefix scan, `SaveSystem`'s slots) do not fit
|
|
10
|
+
* it and keep their own code.
|
|
11
|
+
*/
|
|
12
|
+
export declare class StoredValue<T> {
|
|
13
|
+
private readonly storage;
|
|
14
|
+
private readonly key;
|
|
15
|
+
constructor(storage: SaveStorage, key: string);
|
|
16
|
+
/** the stored value, or `fallback` when nothing has been written yet */
|
|
17
|
+
read(fallback: T): T;
|
|
18
|
+
write(value: T): void;
|
|
19
|
+
remove(): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=StoredValue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StoredValue.d.ts","sourceRoot":"","sources":["../../src/core/StoredValue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;;;GASG;AACH,qBAAa,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YAAY,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAG5C;IAED,wEAAwE;IACxE,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAGnB;IAED,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAEpB;IAED,MAAM,IAAI,IAAI,CAEb;CACD"}
|