@datamoc/mw_games 0.14.0 → 0.15.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/CHANGELOG.md +52 -0
- package/dist/audio/Music.d.ts +27 -0
- package/dist/audio/Music.d.ts.map +1 -1
- package/dist/audio/Music.js +70 -2
- package/dist/audio/Music.js.map +1 -1
- package/dist/audio/Orchestrator.d.ts +4 -0
- package/dist/audio/Orchestrator.d.ts.map +1 -1
- package/dist/audio/Orchestrator.js +12 -0
- package/dist/audio/Orchestrator.js.map +1 -1
- package/dist/audio/Sound.d.ts +9 -0
- package/dist/audio/Sound.d.ts.map +1 -1
- package/dist/audio/Sound.js +18 -0
- package/dist/audio/Sound.js.map +1 -1
- package/dist/core/Settings.d.ts +96 -0
- package/dist/core/Settings.d.ts.map +1 -0
- package/dist/core/Settings.js +189 -0
- package/dist/core/Settings.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 +115 -115
- package/dist/mwl/catalog.d.ts +42 -0
- package/dist/mwl/catalog.d.ts.map +1 -1
- package/dist/mwl/catalog.js +88 -0
- package/dist/mwl/catalog.js.map +1 -1
- package/dist/mwl/index.d.ts +1 -1
- package/dist/mwl/index.d.ts.map +1 -1
- package/dist/two-d/Game.d.ts +40 -0
- package/dist/two-d/Game.d.ts.map +1 -1
- package/dist/two-d/Game.js +66 -3
- package/dist/two-d/Game.js.map +1 -1
- package/dist/two-d/QualityScaler.d.ts +63 -0
- package/dist/two-d/QualityScaler.d.ts.map +1 -0
- package/dist/two-d/QualityScaler.js +92 -0
- package/dist/two-d/QualityScaler.js.map +1 -0
- package/dist/two-d/index.d.ts +3 -1
- package/dist/two-d/index.d.ts.map +1 -1
- package/dist/two-d/index.js +1 -0
- package/dist/two-d/index.js.map +1 -1
- package/dist/two-d/render/SpriteGroup.d.ts +83 -0
- package/dist/two-d/render/SpriteGroup.d.ts.map +1 -0
- package/dist/two-d/render/SpriteGroup.js +107 -0
- package/dist/two-d/render/SpriteGroup.js.map +1 -0
- package/dist/two-d/render/index.d.ts +2 -0
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +1 -0
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/two-d/ui/Meter.d.ts +76 -0
- package/dist/two-d/ui/Meter.d.ts.map +1 -0
- package/dist/two-d/ui/Meter.js +134 -0
- package/dist/two-d/ui/Meter.js.map +1 -0
- package/dist/two-d/ui/SettingsScreen.d.ts +112 -0
- package/dist/two-d/ui/SettingsScreen.d.ts.map +1 -0
- package/dist/two-d/ui/SettingsScreen.js +338 -0
- package/dist/two-d/ui/SettingsScreen.js.map +1 -0
- package/dist/two-d/ui/index.d.ts +4 -0
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js +2 -0
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ the public API may still change between minor versions.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-09-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `mwl.MwlTableReference` and `MwlValidationOptions.tableReferences` - declarative
|
|
15
|
+
cross-table reference checks (item 362, from the Pixel Dungeon study's proposal P21). A
|
|
16
|
+
table column declares `references: { table, column }` or `oneOf: [...]`, and
|
|
17
|
+
`validateCatalog` checks every cell once for all tables, reporting `MWL_TABLE_REFERENCE`
|
|
18
|
+
with the table, the row, and the offending value. The declarations' contents (which column
|
|
19
|
+
points where, which set is legal) stay game data in the consuming repository; absent or
|
|
20
|
+
empty cells are skipped and a `list` cell checks each entry under the table's own
|
|
21
|
+
delimiter.
|
|
22
|
+
|
|
23
|
+
- `core.Settings`/`GameSettings`/`SettingsOptions`/`CustomSettingValue` - persisted
|
|
24
|
+
player settings (item 363): music and sound-effect levels, master mute, zoom
|
|
25
|
+
preference, key bindings applied to `Input` on load, and a game-defined `custom` bag
|
|
26
|
+
(`hints`, `violence`, ...). `defaultSettings` names the fresh-game values,
|
|
27
|
+
`effectiveMusicVolume`/`effectiveSfxVolume` read 0 while muted, and corrupt storage
|
|
28
|
+
reads as defaults.
|
|
29
|
+
|
|
30
|
+
- `two-d/ui.SettingsScreen`/`SettingsCustomRow` - a ready-made settings screen over
|
|
31
|
+
`core.Settings` (item 364): music/sfx sliders, mute checkbox, zoom slider, one row
|
|
32
|
+
per `custom` descriptor (boolean/number/choice), an embedded controls page that
|
|
33
|
+
writes rebinds back into `Settings` on exit, and a reset row.
|
|
34
|
+
|
|
35
|
+
- Auto-pause and auto-mute on page hide (item 365): `Game` suspends the loop and
|
|
36
|
+
silences its `audio` rig while hidden and resumes both on show (`autoPause`, on by
|
|
37
|
+
default), via `suspend`/`resume` that park the clock at 0 and route through the top
|
|
38
|
+
scene's `onSuspend`/`onResume`. `Music`/`Sound`/`Orchestrator` gain matching
|
|
39
|
+
`suspend`/`resume`, and the player's own mute setting is never touched.
|
|
40
|
+
|
|
41
|
+
- Frame-time quality scaling over the static device fit (item 366): a `QualityScaler`
|
|
42
|
+
steps the backing-store ratio down after sustained over-budget frames and back up
|
|
43
|
+
only after a much longer streak inside budget, wired to `Game` via the
|
|
44
|
+
`qualityScaling` option (off unless given) with the fitted ratio as its ceiling.
|
|
45
|
+
|
|
46
|
+
- Audio ducking for dialogue and menus (item 367): `Music.duck` glides to an
|
|
47
|
+
attenuated level until `unduck`, through the existing fade path, with tracks started
|
|
48
|
+
while ducked starting ducked and pending fades retargeted. `Sound` is untouched.
|
|
49
|
+
|
|
50
|
+
- A discrete meter widget, `two-d/ui.Meter` (item 368): N icons with a value filled,
|
|
51
|
+
the heart row or star rating to `Bar`'s continuous fill. Fractions clip mid-icon
|
|
52
|
+
through a mask, so halves need no extra art; flat theme pips by default, a
|
|
53
|
+
game-supplied filled/empty texture pair otherwise.
|
|
54
|
+
|
|
55
|
+
- Animate only what is on screen, `two-d/render.SpriteGroup`/`isOnScreen` (item 369):
|
|
56
|
+
`update(camera, dt)` advances the members inside `camera.view` (plus `margin`) and leaves
|
|
57
|
+
the rest on their current frame. Members are `AnimatedSprite`s, `ParticleEmitter`s, or
|
|
58
|
+
anything with `x`/`y` in world units plus `update(dt)`; skipped members resume where they
|
|
59
|
+
left off, and a rotated camera tests the same over-inclusive box `TileMap.cull` uses, so
|
|
60
|
+
nothing visible ever freezes.
|
|
61
|
+
|
|
10
62
|
## [0.14.0] - 2026-09-15
|
|
11
63
|
|
|
12
64
|
### Added
|
package/dist/audio/Music.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare class Music {
|
|
|
26
26
|
private trackQueue;
|
|
27
27
|
private playlistFade;
|
|
28
28
|
volume: number;
|
|
29
|
+
private suspended_;
|
|
30
|
+
private duckLevel_;
|
|
29
31
|
constructor(options?: MusicOptions);
|
|
30
32
|
/** @param fadeDuration seconds; 0 switches immediately with no crossfade */
|
|
31
33
|
play(path: string, fadeDuration?: number): void;
|
|
@@ -38,6 +40,31 @@ export declare class Music {
|
|
|
38
40
|
private startNextTrack;
|
|
39
41
|
private start;
|
|
40
42
|
stop(fadeDuration?: number): void;
|
|
43
|
+
/**
|
|
44
|
+
* Silences everything for a page hide: pauses the current track and every fading one,
|
|
45
|
+
* and freezes `update` until `resume`, so a crossfade mid-flight continues where it
|
|
46
|
+
* stopped rather than finishing inaudibly in a background tab. Idempotent.
|
|
47
|
+
*/
|
|
48
|
+
suspend(): void;
|
|
49
|
+
/**
|
|
50
|
+
* The counterpart to `suspend`: restarts the current track where it paused. A track
|
|
51
|
+
* stopped (not suspended) while hidden stays stopped - resume only resumes.
|
|
52
|
+
*/
|
|
53
|
+
resume(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Ducks the music under dialogue or a menu: the current track glides to
|
|
56
|
+
* `volume * level` over `fadeDuration` and stays there until `unduck`, and tracks
|
|
57
|
+
* started while ducked start ducked. Pending fades are retargeted proportionally, so
|
|
58
|
+
* ducking mid-crossfade still lands quiet. `Sound` is untouched. Idempotent.
|
|
59
|
+
*
|
|
60
|
+
* @param level 0 (silent) to 1 (full); clamped, 0.5 by default
|
|
61
|
+
*/
|
|
62
|
+
duck(level?: number, fadeDuration?: number): void;
|
|
63
|
+
/** releases a `duck` back to full volume through the same fade path */
|
|
64
|
+
unduck(fadeDuration?: number): void;
|
|
65
|
+
/** the held duck attenuation, 1 when full */
|
|
66
|
+
get duckLevel(): number;
|
|
67
|
+
get isSuspended(): boolean;
|
|
41
68
|
update(dt: number): void;
|
|
42
69
|
}
|
|
43
70
|
//# sourceMappingURL=Music.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Music.d.ts","sourceRoot":"","sources":["../../src/audio/Music.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAW3D,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;CACpC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Music.d.ts","sourceRoot":"","sources":["../../src/audio/Music.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAW3D,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;CACpC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAK;IAEvB,YAAY,OAAO,GAAE,YAAiB,EAGrC;IAED,4EAA4E;IAC5E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,IAAI,CAGzC;IAED;;;;OAIG;IACH,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,SAAI,GAAG,IAAI,CAQ3D;IAED,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,KAAK;IA8Cb,IAAI,CAAC,YAAY,SAAI,GAAG,IAAI,CAW3B;IAED;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAId;IAED;;;OAGG;IACH,MAAM,IAAI,IAAI,CAIb;IAED;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,SAAM,EAAE,YAAY,SAAM,GAAG,IAAI,CAqB1C;IAED,uEAAuE;IACvE,MAAM,CAAC,YAAY,SAAM,GAAG,IAAI,CAE/B;IAED,6CAA6C;IAC7C,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAgBvB;CACD"}
|
package/dist/audio/Music.js
CHANGED
|
@@ -21,6 +21,8 @@ export class Music {
|
|
|
21
21
|
this.fades = [];
|
|
22
22
|
this.trackQueue = null;
|
|
23
23
|
this.playlistFade = 1;
|
|
24
|
+
this.suspended_ = false;
|
|
25
|
+
this.duckLevel_ = 1;
|
|
24
26
|
this.volume = options.volume ?? 1;
|
|
25
27
|
this.create = options.create ?? createAudio;
|
|
26
28
|
}
|
|
@@ -63,7 +65,7 @@ export class Music {
|
|
|
63
65
|
const previous = this.current;
|
|
64
66
|
this.current = incoming;
|
|
65
67
|
if (fadeDuration <= 0) {
|
|
66
|
-
incoming.volume = this.volume;
|
|
68
|
+
incoming.volume = this.volume * this.duckLevel_;
|
|
67
69
|
// a rejection here means the switch itself (or the previous.pause() right below)
|
|
68
70
|
// interrupted this play()'s own in-flight load - expected during a track switch,
|
|
69
71
|
// so swallow it the standard way rather than let it surface as unhandled.
|
|
@@ -78,7 +80,7 @@ export class Music {
|
|
|
78
80
|
elapsed: 0,
|
|
79
81
|
duration: fadeDuration,
|
|
80
82
|
from: 0,
|
|
81
|
-
to: this.volume,
|
|
83
|
+
to: this.volume * this.duckLevel_,
|
|
82
84
|
stopAtEnd: false,
|
|
83
85
|
});
|
|
84
86
|
if (previous) {
|
|
@@ -104,7 +106,73 @@ export class Music {
|
|
|
104
106
|
}
|
|
105
107
|
this.fades.push({ audio, elapsed: 0, duration: fadeDuration, from: audio.volume, to: 0, stopAtEnd: true });
|
|
106
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Silences everything for a page hide: pauses the current track and every fading one,
|
|
111
|
+
* and freezes `update` until `resume`, so a crossfade mid-flight continues where it
|
|
112
|
+
* stopped rather than finishing inaudibly in a background tab. Idempotent.
|
|
113
|
+
*/
|
|
114
|
+
suspend() {
|
|
115
|
+
this.suspended_ = true;
|
|
116
|
+
this.current?.pause();
|
|
117
|
+
for (const fade of this.fades)
|
|
118
|
+
fade.audio.pause();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The counterpart to `suspend`: restarts the current track where it paused. A track
|
|
122
|
+
* stopped (not suspended) while hidden stays stopped - resume only resumes.
|
|
123
|
+
*/
|
|
124
|
+
resume() {
|
|
125
|
+
if (!this.suspended_)
|
|
126
|
+
return;
|
|
127
|
+
this.suspended_ = false;
|
|
128
|
+
if (this.current)
|
|
129
|
+
void Promise.resolve(this.current.play()).catch(() => { });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Ducks the music under dialogue or a menu: the current track glides to
|
|
133
|
+
* `volume * level` over `fadeDuration` and stays there until `unduck`, and tracks
|
|
134
|
+
* started while ducked start ducked. Pending fades are retargeted proportionally, so
|
|
135
|
+
* ducking mid-crossfade still lands quiet. `Sound` is untouched. Idempotent.
|
|
136
|
+
*
|
|
137
|
+
* @param level 0 (silent) to 1 (full); clamped, 0.5 by default
|
|
138
|
+
*/
|
|
139
|
+
duck(level = 0.5, fadeDuration = 0.5) {
|
|
140
|
+
const next = Number.isFinite(level) ? Math.max(0, Math.min(1, level)) : 1;
|
|
141
|
+
const previous = this.duckLevel_;
|
|
142
|
+
this.duckLevel_ = next;
|
|
143
|
+
for (const fade of this.fades) {
|
|
144
|
+
fade.to = previous > 0 ? (fade.to / previous) * next : this.volume * next;
|
|
145
|
+
}
|
|
146
|
+
if (this.current) {
|
|
147
|
+
const target = this.volume * next;
|
|
148
|
+
if (fadeDuration <= 0)
|
|
149
|
+
this.current.volume = target;
|
|
150
|
+
else {
|
|
151
|
+
this.fades.push({
|
|
152
|
+
audio: this.current,
|
|
153
|
+
elapsed: 0,
|
|
154
|
+
duration: fadeDuration,
|
|
155
|
+
from: this.current.volume,
|
|
156
|
+
to: target,
|
|
157
|
+
stopAtEnd: false,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** releases a `duck` back to full volume through the same fade path */
|
|
163
|
+
unduck(fadeDuration = 0.5) {
|
|
164
|
+
this.duck(1, fadeDuration);
|
|
165
|
+
}
|
|
166
|
+
/** the held duck attenuation, 1 when full */
|
|
167
|
+
get duckLevel() {
|
|
168
|
+
return this.duckLevel_;
|
|
169
|
+
}
|
|
170
|
+
get isSuspended() {
|
|
171
|
+
return this.suspended_;
|
|
172
|
+
}
|
|
107
173
|
update(dt) {
|
|
174
|
+
if (this.suspended_)
|
|
175
|
+
return;
|
|
108
176
|
//iterate a copy: a fade finishing may not touch the list, but this stays safe if one day it does
|
|
109
177
|
for (const fade of [...this.fades]) {
|
|
110
178
|
fade.elapsed += dt;
|
package/dist/audio/Music.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Music.js","sourceRoot":"","sources":["../../src/audio/Music.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAgB3D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,KAAK;
|
|
1
|
+
{"version":3,"file":"Music.js","sourceRoot":"","sources":["../../src/audio/Music.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,5 +50,9 @@ export declare class Orchestrator {
|
|
|
50
50
|
on(event: string, cue: Sound): void;
|
|
51
51
|
/** plays the sound registered for `event`, if any; silently does nothing otherwise */
|
|
52
52
|
trigger(event: string): void;
|
|
53
|
+
/** suspends the music and every cue - the rig a `Game` silences on page hide */
|
|
54
|
+
suspend(): void;
|
|
55
|
+
/** resumes the music and every cue after `suspend` */
|
|
56
|
+
resume(): void;
|
|
53
57
|
}
|
|
54
58
|
//# sourceMappingURL=Orchestrator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/audio/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,2GAA2G;AAC3G,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,KAAK,CAAQ;IAErB,YAAY,KAAK,EAAE,KAAK,EAEvB;IAED,oDAAoD;IACpD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAEtD;IAED,yFAAyF;IACzF,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAQzB;IAED,8DAA8D;IAC9D,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,CAElC;IAED,sFAAsF;IACtF,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;CACD"}
|
|
1
|
+
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/audio/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,2GAA2G;AAC3G,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,KAAK,CAAQ;IAErB,YAAY,KAAK,EAAE,KAAK,EAEvB;IAED,oDAAoD;IACpD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAEtD;IAED,yFAAyF;IACzF,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAQzB;IAED,8DAA8D;IAC9D,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,CAElC;IAED,sFAAsF;IACtF,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,gFAAgF;IAChF,OAAO,IAAI,IAAI,CAGd;IAED,sDAAsD;IACtD,MAAM,IAAI,IAAI,CAGb;CACD"}
|
|
@@ -58,5 +58,17 @@ export class Orchestrator {
|
|
|
58
58
|
trigger(event) {
|
|
59
59
|
this.cues.get(event)?.play();
|
|
60
60
|
}
|
|
61
|
+
/** suspends the music and every cue - the rig a `Game` silences on page hide */
|
|
62
|
+
suspend() {
|
|
63
|
+
this.music.suspend();
|
|
64
|
+
for (const cue of this.cues.values())
|
|
65
|
+
cue.suspend();
|
|
66
|
+
}
|
|
67
|
+
/** resumes the music and every cue after `suspend` */
|
|
68
|
+
resume() {
|
|
69
|
+
this.music.resume();
|
|
70
|
+
for (const cue of this.cues.values())
|
|
71
|
+
cue.resume();
|
|
72
|
+
}
|
|
61
73
|
}
|
|
62
74
|
//# sourceMappingURL=Orchestrator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.js","sourceRoot":"","sources":["../../src/audio/Orchestrator.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAO,YAAY;IAMxB,YAAY,KAAY;QALhB,WAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;QAC9C,SAAI,GAAG,IAAI,GAAG,EAAiB,CAAC;QAChC,iBAAY,GAAkB,IAAI,CAAC;QAI1C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,oDAAoD;IACpD,MAAM,CAAC,KAAa,EAAE,OAA0B;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,yFAAyF;IACzF,KAAK,CAAC,KAAa;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,KAAK,GAAG,CAAC,CAAC;QAE/E,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,YAAY;YAAE,OAAO;QAEhD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;IACnC,CAAC;IAED,8DAA8D;IAC9D,EAAE,CAAC,KAAa,EAAE,GAAU;QAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,sFAAsF;IACtF,OAAO,CAAC,KAAa;QACpB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9B,CAAC;CACD"}
|
|
1
|
+
{"version":3,"file":"Orchestrator.js","sourceRoot":"","sources":["../../src/audio/Orchestrator.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAO,YAAY;IAMxB,YAAY,KAAY;QALhB,WAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;QAC9C,SAAI,GAAG,IAAI,GAAG,EAAiB,CAAC;QAChC,iBAAY,GAAkB,IAAI,CAAC;QAI1C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,oDAAoD;IACpD,MAAM,CAAC,KAAa,EAAE,OAA0B;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,yFAAyF;IACzF,KAAK,CAAC,KAAa;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,KAAK,GAAG,CAAC,CAAC;QAE/E,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,YAAY;YAAE,OAAO;QAEhD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;IACnC,CAAC;IAED,8DAA8D;IAC9D,EAAE,CAAC,KAAa,EAAE,GAAU;QAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,sFAAsF;IACtF,OAAO,CAAC,KAAa;QACpB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,gFAAgF;IAChF,OAAO;QACN,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,sDAAsD;IACtD,MAAM;QACL,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACpD,CAAC;CACD"}
|
package/dist/audio/Sound.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare class Sound {
|
|
|
26
26
|
private pool;
|
|
27
27
|
private next;
|
|
28
28
|
private caption?;
|
|
29
|
+
private suspended_;
|
|
29
30
|
volume: number;
|
|
30
31
|
constructor(path: string, options?: SoundOptions);
|
|
31
32
|
/**
|
|
@@ -42,5 +43,13 @@ export declare class Sound {
|
|
|
42
43
|
*/
|
|
43
44
|
play(gain?: number, pitch?: number): void;
|
|
44
45
|
stopAll(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Cuts in-flight one-shots and silences later `play` calls until `resume` - the page-hide
|
|
48
|
+
* half of `Music.suspend`. One-shots have nothing to restart, so resume only re-enables.
|
|
49
|
+
*/
|
|
50
|
+
suspend(): void;
|
|
51
|
+
/** re-enables `play` after `suspend` */
|
|
52
|
+
resume(): void;
|
|
53
|
+
get isSuspended(): boolean;
|
|
45
54
|
}
|
|
46
55
|
//# sourceMappingURL=Sound.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,EAMnD;IAED;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,IAAI,SAAI,EAAE,KAAK,SAAI,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,EAMnD;IAED;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,IAAI,SAAI,EAAE,KAAK,SAAI,GAAG,IAAI,CAc9B;IAED,OAAO,IAAI,IAAI,CAKd;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAGd;IAED,wCAAwC;IACxC,MAAM,IAAI,IAAI,CAEb;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;CACD"}
|
package/dist/audio/Sound.js
CHANGED
|
@@ -17,6 +17,7 @@ import { onCaption } from './Captions.js';
|
|
|
17
17
|
export class Sound {
|
|
18
18
|
constructor(path, options = {}) {
|
|
19
19
|
this.next = 0;
|
|
20
|
+
this.suspended_ = false;
|
|
20
21
|
const size = Math.max(1, options.poolSize ?? 4);
|
|
21
22
|
const create = options.create ?? createAudio;
|
|
22
23
|
this.volume = options.volume ?? 1;
|
|
@@ -36,6 +37,8 @@ export class Sound {
|
|
|
36
37
|
* than own a panner node a game does not necessarily want - the same reasoning applies here.
|
|
37
38
|
*/
|
|
38
39
|
play(gain = 1, pitch = 1) {
|
|
40
|
+
if (this.suspended_)
|
|
41
|
+
return;
|
|
39
42
|
const audio = this.pool[this.next];
|
|
40
43
|
this.next = (this.next + 1) % this.pool.length;
|
|
41
44
|
audio.volume = this.volume * gain;
|
|
@@ -54,5 +57,20 @@ export class Sound {
|
|
|
54
57
|
audio.currentTime = 0;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Cuts in-flight one-shots and silences later `play` calls until `resume` - the page-hide
|
|
62
|
+
* half of `Music.suspend`. One-shots have nothing to restart, so resume only re-enables.
|
|
63
|
+
*/
|
|
64
|
+
suspend() {
|
|
65
|
+
this.suspended_ = true;
|
|
66
|
+
this.stopAll();
|
|
67
|
+
}
|
|
68
|
+
/** re-enables `play` after `suspend` */
|
|
69
|
+
resume() {
|
|
70
|
+
this.suspended_ = false;
|
|
71
|
+
}
|
|
72
|
+
get isSuspended() {
|
|
73
|
+
return this.suspended_;
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
//# sourceMappingURL=Sound.js.map
|
package/dist/audio/Sound.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAc1C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,KAAK;
|
|
1
|
+
{"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAc1C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,KAAK;IAOjB,YAAY,IAAY,EAAE,OAAO,GAAiB,EAAE;QAL5C,SAAI,GAAG,CAAC,CAAC;QAET,eAAU,GAAG,KAAK,CAAC;QAI1B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC;QACvB,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAE/C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAClC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC;QAC3B,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACtB,mFAAmF;QACnF,qFAAqF;QACrF,wFAAwF;QACxF,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnD,IAAI,IAAI,CAAC,OAAO;YAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO;QACN,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,OAAO;QACN,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,wCAAwC;IACxC,MAAM;QACL,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;CACD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type SaveStorage } from './Save.ts';
|
|
2
|
+
import { type Action } from './Input.ts';
|
|
3
|
+
/** a game-defined value: hints on/off, a violence level, anything JSON-flat */
|
|
4
|
+
export type CustomSettingValue = string | number | boolean;
|
|
5
|
+
/** the player-facing values `Settings` persists; everything is renderer-free on purpose */
|
|
6
|
+
export interface GameSettings {
|
|
7
|
+
/** background-music level, 0 to 1 */
|
|
8
|
+
musicVolume: number;
|
|
9
|
+
/** sound-effect level, 0 to 1 */
|
|
10
|
+
sfxVolume: number;
|
|
11
|
+
/** master mute: both effective volumes read 0 while true, levels are kept */
|
|
12
|
+
muted: boolean;
|
|
13
|
+
/** the zoom a game applies to its own `Camera`; never below 0.01, like `Camera` itself */
|
|
14
|
+
zoom: number;
|
|
15
|
+
/** key bindings, in the shape `Input.exportBindings` writes and `importBindings` reads */
|
|
16
|
+
bindings: Record<Action, string[]>;
|
|
17
|
+
/** game-defined values beyond the built-ins above (`hints`, `violence`, ...), keyed by name */
|
|
18
|
+
custom: Record<string, CustomSettingValue>;
|
|
19
|
+
}
|
|
20
|
+
export interface SettingsOptions {
|
|
21
|
+
/** namespaces the storage key, so two games sharing an origin never collide */
|
|
22
|
+
namespace?: string;
|
|
23
|
+
storage?: SaveStorage;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The values a fresh game starts with: full volume, unmuted, zoom 1, default bindings.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { defaultSettings } from '@datamoc/mw_games/core';
|
|
31
|
+
*
|
|
32
|
+
* console.log(defaultSettings().zoom); // 1
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function defaultSettings(): GameSettings;
|
|
36
|
+
/** what a game hands its own audio objects: 0 while muted, the stored level otherwise */
|
|
37
|
+
export declare function effectiveMusicVolume(settings: Pick<GameSettings, 'musicVolume' | 'muted'>): number;
|
|
38
|
+
/** what a game hands its own sound effects: 0 while muted, the stored level otherwise */
|
|
39
|
+
export declare function effectiveSfxVolume(settings: Pick<GameSettings, 'sfxVolume' | 'muted'>): number;
|
|
40
|
+
/**
|
|
41
|
+
* Persisted player settings: music and sound-effect levels, master mute, zoom preference,
|
|
42
|
+
* and key bindings, read back on the next launch.
|
|
43
|
+
*
|
|
44
|
+
* The values live in `core` so they stay renderer-free; the wiring stays with the game,
|
|
45
|
+
* which assigns them onto its own `Music`/`Sound`/`Camera` (see the example). Bindings are
|
|
46
|
+
* the exception: they are applied to `Input` on load and on every bindings mutation,
|
|
47
|
+
* since persisted keys that a game must remember to apply would silently do nothing.
|
|
48
|
+
*
|
|
49
|
+
* Corrupt storage reads as defaults rather than throwing: a settings screen should show
|
|
50
|
+
* the defaults, not a crash.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { Settings, effectiveMusicVolume, effectiveSfxVolume } from '@datamoc/mw_games/core';
|
|
55
|
+
*
|
|
56
|
+
* const settings = new Settings({ namespace: 'my-game' });
|
|
57
|
+
* settings.update({ musicVolume: 0.7, zoom: 2 });
|
|
58
|
+
* settings.setCustom('hints', true);
|
|
59
|
+
*
|
|
60
|
+
* const music = { volume: 1 };
|
|
61
|
+
* const camera = { zoom: 1 };
|
|
62
|
+
* music.volume = effectiveMusicVolume(settings.current);
|
|
63
|
+
* camera.zoom = settings.current.zoom;
|
|
64
|
+
* console.log(effectiveSfxVolume(settings.current), settings.getCustom('hints', false));
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare class Settings {
|
|
68
|
+
private readonly storage;
|
|
69
|
+
private readonly key;
|
|
70
|
+
private value;
|
|
71
|
+
constructor(options?: SettingsOptions);
|
|
72
|
+
/** a copy of the current values; mutating it writes nothing back, use the setters */
|
|
73
|
+
get current(): GameSettings;
|
|
74
|
+
/** a game-defined value (`hints`, `violence`, ...), or `fallback` when never set */
|
|
75
|
+
getCustom(key: string, fallback: CustomSettingValue): CustomSettingValue;
|
|
76
|
+
/** stores a game-defined value and persists it */
|
|
77
|
+
setCustom(key: string, value: CustomSettingValue): void;
|
|
78
|
+
setMusicVolume(volume: number): void;
|
|
79
|
+
setSfxVolume(volume: number): void;
|
|
80
|
+
setMuted(muted: boolean): void;
|
|
81
|
+
setZoom(zoom: number): void;
|
|
82
|
+
setBindings(bindings: Readonly<Record<Action, readonly string[]>>): void;
|
|
83
|
+
update(patch: {
|
|
84
|
+
musicVolume?: number;
|
|
85
|
+
sfxVolume?: number;
|
|
86
|
+
muted?: boolean;
|
|
87
|
+
zoom?: number;
|
|
88
|
+
bindings?: Readonly<Record<Action, readonly string[]>>;
|
|
89
|
+
custom?: Readonly<Record<string, CustomSettingValue>>;
|
|
90
|
+
}): void;
|
|
91
|
+
/** re-applies the stored bindings to `Input`, for a game that installed its own after this was built */
|
|
92
|
+
applyBindings(): void;
|
|
93
|
+
/** back to full volume, unmuted, zoom 1 and the default bindings, persisted */
|
|
94
|
+
reset(): void;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=Settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../src/core/Settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAoC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAG3E,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC5B,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;IACf,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,+FAA+F;IAC/F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,eAAe;IAC/B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAS9C;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,MAAM,CAElG;AAED,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,MAAM,CAE9F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAe;IAE5B,YAAY,OAAO,GAAE,eAAoB,EAKxC;IAED,qFAAqF;IACrF,IAAI,OAAO,IAAI,YAAY,CAE1B;IAED,oFAAoF;IACpF,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAEvE;IAED,kDAAkD;IAClD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAGtD;IAED,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEnC;IAED,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjC;IAED,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE7B;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE1B;IAED,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI,CAEvE;IAED,MAAM,CAAC,KAAK,EAAE;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;KACtD,GAAG,IAAI,CAWP;IAED,wGAAwG;IACxG,aAAa,IAAI,IAAI,CAEpB;IAED,+EAA+E;IAC/E,KAAK,IAAI,IAAI,CAIZ;CACD"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { defaultStorage } from './Save.js';
|
|
2
|
+
import { DEFAULT_BINDINGS, importBindings } from './Input.js';
|
|
3
|
+
import { clamp } from './Math.js';
|
|
4
|
+
/**
|
|
5
|
+
* The values a fresh game starts with: full volume, unmuted, zoom 1, default bindings.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { defaultSettings } from '@datamoc/mw_games/core';
|
|
10
|
+
*
|
|
11
|
+
* console.log(defaultSettings().zoom); // 1
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function defaultSettings() {
|
|
15
|
+
return {
|
|
16
|
+
musicVolume: 1,
|
|
17
|
+
sfxVolume: 1,
|
|
18
|
+
muted: false,
|
|
19
|
+
zoom: 1,
|
|
20
|
+
bindings: copyBindings(DEFAULT_BINDINGS),
|
|
21
|
+
custom: {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** what a game hands its own audio objects: 0 while muted, the stored level otherwise */
|
|
25
|
+
export function effectiveMusicVolume(settings) {
|
|
26
|
+
return settings.muted ? 0 : settings.musicVolume;
|
|
27
|
+
}
|
|
28
|
+
/** what a game hands its own sound effects: 0 while muted, the stored level otherwise */
|
|
29
|
+
export function effectiveSfxVolume(settings) {
|
|
30
|
+
return settings.muted ? 0 : settings.sfxVolume;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Persisted player settings: music and sound-effect levels, master mute, zoom preference,
|
|
34
|
+
* and key bindings, read back on the next launch.
|
|
35
|
+
*
|
|
36
|
+
* The values live in `core` so they stay renderer-free; the wiring stays with the game,
|
|
37
|
+
* which assigns them onto its own `Music`/`Sound`/`Camera` (see the example). Bindings are
|
|
38
|
+
* the exception: they are applied to `Input` on load and on every bindings mutation,
|
|
39
|
+
* since persisted keys that a game must remember to apply would silently do nothing.
|
|
40
|
+
*
|
|
41
|
+
* Corrupt storage reads as defaults rather than throwing: a settings screen should show
|
|
42
|
+
* the defaults, not a crash.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { Settings, effectiveMusicVolume, effectiveSfxVolume } from '@datamoc/mw_games/core';
|
|
47
|
+
*
|
|
48
|
+
* const settings = new Settings({ namespace: 'my-game' });
|
|
49
|
+
* settings.update({ musicVolume: 0.7, zoom: 2 });
|
|
50
|
+
* settings.setCustom('hints', true);
|
|
51
|
+
*
|
|
52
|
+
* const music = { volume: 1 };
|
|
53
|
+
* const camera = { zoom: 1 };
|
|
54
|
+
* music.volume = effectiveMusicVolume(settings.current);
|
|
55
|
+
* camera.zoom = settings.current.zoom;
|
|
56
|
+
* console.log(effectiveSfxVolume(settings.current), settings.getCustom('hints', false));
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class Settings {
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
this.storage = options.storage ?? defaultStorage();
|
|
62
|
+
this.key = `mwg-settings:${options.namespace ?? 'default'}`;
|
|
63
|
+
this.value = readSettings(this.storage, this.key);
|
|
64
|
+
importBindings(this.value.bindings);
|
|
65
|
+
}
|
|
66
|
+
/** a copy of the current values; mutating it writes nothing back, use the setters */
|
|
67
|
+
get current() {
|
|
68
|
+
return { ...this.value, bindings: copyBindings(this.value.bindings), custom: { ...this.value.custom } };
|
|
69
|
+
}
|
|
70
|
+
/** a game-defined value (`hints`, `violence`, ...), or `fallback` when never set */
|
|
71
|
+
getCustom(key, fallback) {
|
|
72
|
+
return this.value.custom[key] ?? fallback;
|
|
73
|
+
}
|
|
74
|
+
/** stores a game-defined value and persists it */
|
|
75
|
+
setCustom(key, value) {
|
|
76
|
+
this.value.custom[key] = value;
|
|
77
|
+
writeSettings(this.storage, this.key, this.value);
|
|
78
|
+
}
|
|
79
|
+
setMusicVolume(volume) {
|
|
80
|
+
this.update({ musicVolume: volume });
|
|
81
|
+
}
|
|
82
|
+
setSfxVolume(volume) {
|
|
83
|
+
this.update({ sfxVolume: volume });
|
|
84
|
+
}
|
|
85
|
+
setMuted(muted) {
|
|
86
|
+
this.update({ muted });
|
|
87
|
+
}
|
|
88
|
+
setZoom(zoom) {
|
|
89
|
+
this.update({ zoom });
|
|
90
|
+
}
|
|
91
|
+
setBindings(bindings) {
|
|
92
|
+
this.update({ bindings });
|
|
93
|
+
}
|
|
94
|
+
update(patch) {
|
|
95
|
+
if (patch.musicVolume !== undefined)
|
|
96
|
+
this.value.musicVolume = clampVolume(patch.musicVolume);
|
|
97
|
+
if (patch.sfxVolume !== undefined)
|
|
98
|
+
this.value.sfxVolume = clampVolume(patch.sfxVolume);
|
|
99
|
+
if (patch.muted !== undefined)
|
|
100
|
+
this.value.muted = patch.muted;
|
|
101
|
+
if (patch.zoom !== undefined)
|
|
102
|
+
this.value.zoom = clampZoom(patch.zoom);
|
|
103
|
+
if (patch.bindings !== undefined) {
|
|
104
|
+
this.value.bindings = sanitizeBindings(patch.bindings);
|
|
105
|
+
importBindings(this.value.bindings);
|
|
106
|
+
}
|
|
107
|
+
if (patch.custom !== undefined)
|
|
108
|
+
this.value.custom = sanitizeCustom(patch.custom);
|
|
109
|
+
writeSettings(this.storage, this.key, this.value);
|
|
110
|
+
}
|
|
111
|
+
/** re-applies the stored bindings to `Input`, for a game that installed its own after this was built */
|
|
112
|
+
applyBindings() {
|
|
113
|
+
importBindings(this.value.bindings);
|
|
114
|
+
}
|
|
115
|
+
/** back to full volume, unmuted, zoom 1 and the default bindings, persisted */
|
|
116
|
+
reset() {
|
|
117
|
+
this.value = defaultSettings();
|
|
118
|
+
importBindings(this.value.bindings);
|
|
119
|
+
writeSettings(this.storage, this.key, this.value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function clampVolume(value) {
|
|
123
|
+
return Number.isFinite(value) ? clamp(value, 0, 1) : 1;
|
|
124
|
+
}
|
|
125
|
+
function clampZoom(value) {
|
|
126
|
+
return Number.isFinite(value) ? Math.max(0.01, value) : 1;
|
|
127
|
+
}
|
|
128
|
+
function copyBindings(bindings) {
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const [action, keys] of Object.entries(bindings))
|
|
131
|
+
out[action] = [...keys];
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function sanitizeBindings(raw) {
|
|
135
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
136
|
+
return copyBindings(DEFAULT_BINDINGS);
|
|
137
|
+
const out = {};
|
|
138
|
+
for (const [action, keys] of Object.entries(raw)) {
|
|
139
|
+
if (!Array.isArray(keys))
|
|
140
|
+
continue;
|
|
141
|
+
const codes = keys.filter((key) => typeof key === 'string');
|
|
142
|
+
if (codes.length > 0)
|
|
143
|
+
out[action] = codes;
|
|
144
|
+
}
|
|
145
|
+
return Object.keys(out).length > 0 ? out : copyBindings(DEFAULT_BINDINGS);
|
|
146
|
+
}
|
|
147
|
+
function isCustomValue(raw) {
|
|
148
|
+
const kind = typeof raw;
|
|
149
|
+
return kind === 'string' || kind === 'number' || kind === 'boolean';
|
|
150
|
+
}
|
|
151
|
+
function sanitizeCustom(raw) {
|
|
152
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
153
|
+
return {};
|
|
154
|
+
const out = {};
|
|
155
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
156
|
+
if (isCustomValue(value))
|
|
157
|
+
out[key] = value;
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
function sanitizeSettings(raw) {
|
|
162
|
+
const defaults = defaultSettings();
|
|
163
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
164
|
+
return defaults;
|
|
165
|
+
const candidate = raw;
|
|
166
|
+
return {
|
|
167
|
+
musicVolume: typeof candidate.musicVolume === 'number' ? clampVolume(candidate.musicVolume) : defaults.musicVolume,
|
|
168
|
+
sfxVolume: typeof candidate.sfxVolume === 'number' ? clampVolume(candidate.sfxVolume) : defaults.sfxVolume,
|
|
169
|
+
muted: typeof candidate.muted === 'boolean' ? candidate.muted : defaults.muted,
|
|
170
|
+
zoom: typeof candidate.zoom === 'number' ? clampZoom(candidate.zoom) : defaults.zoom,
|
|
171
|
+
bindings: sanitizeBindings(candidate.bindings),
|
|
172
|
+
custom: sanitizeCustom(candidate.custom),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function readSettings(storage, key) {
|
|
176
|
+
const raw = storage.read(key);
|
|
177
|
+
if (!raw)
|
|
178
|
+
return defaultSettings();
|
|
179
|
+
try {
|
|
180
|
+
return sanitizeSettings(JSON.parse(raw));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return defaultSettings();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function writeSettings(storage, key, value) {
|
|
187
|
+
storage.write(key, JSON.stringify(value));
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=Settings.js.map
|