@datamoc/mw_games 0.4.2 → 0.4.3
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 +1 -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/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/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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export class SelectionModel {
|
|
2
|
+
constructor(hooks) {
|
|
3
|
+
this.list = [];
|
|
4
|
+
this.current = 0;
|
|
5
|
+
this.hooks = hooks;
|
|
6
|
+
}
|
|
7
|
+
get items() {
|
|
8
|
+
return this.list;
|
|
9
|
+
}
|
|
10
|
+
get selectedIndex() {
|
|
11
|
+
return this.current;
|
|
12
|
+
}
|
|
13
|
+
get selected() {
|
|
14
|
+
return this.list[this.current] ?? null;
|
|
15
|
+
}
|
|
16
|
+
get length() {
|
|
17
|
+
return this.list.length;
|
|
18
|
+
}
|
|
19
|
+
/** points at the first selectable entry (index 0 when nothing is), without notifying */
|
|
20
|
+
reset(items) {
|
|
21
|
+
this.list = items;
|
|
22
|
+
this.current = items.findIndex((item) => !item.disabled);
|
|
23
|
+
if (this.current === -1)
|
|
24
|
+
this.current = 0;
|
|
25
|
+
}
|
|
26
|
+
/** swaps two entries in place, the way grid reordering does; the highlight is untouched */
|
|
27
|
+
swap(a, b) {
|
|
28
|
+
[this.list[a], this.list[b]] = [this.list[b], this.list[a]];
|
|
29
|
+
}
|
|
30
|
+
land(index) {
|
|
31
|
+
this.current = index;
|
|
32
|
+
this.hooks.onChange();
|
|
33
|
+
const item = this.list[index];
|
|
34
|
+
if (item)
|
|
35
|
+
this.hooks.onHighlight(item, index);
|
|
36
|
+
}
|
|
37
|
+
select(index) {
|
|
38
|
+
if (index < 0 || index >= this.list.length || this.list[index].disabled)
|
|
39
|
+
return;
|
|
40
|
+
this.land(index);
|
|
41
|
+
}
|
|
42
|
+
confirm(onSelect) {
|
|
43
|
+
const item = this.selected;
|
|
44
|
+
if (!item || item.disabled)
|
|
45
|
+
return false;
|
|
46
|
+
onSelect?.(item, this.current);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Steps from the current index with `advance`, skipping disabled and out-of-range
|
|
51
|
+
* landings. Each attempt starts from the previous candidate (not the selection), so a
|
|
52
|
+
* blocked neighbour keeps walking rather than retrying the same cell. Tries at most
|
|
53
|
+
* `maxSteps` candidates, so an all-disabled set terminates. Returns false when there
|
|
54
|
+
* is nothing selectable to move to.
|
|
55
|
+
*/
|
|
56
|
+
step(advance, maxSteps) {
|
|
57
|
+
if (this.list.length === 0)
|
|
58
|
+
return false;
|
|
59
|
+
let from = this.current;
|
|
60
|
+
for (let tried = 0; tried < maxSteps; tried++) {
|
|
61
|
+
const next = advance(from, this.list.length);
|
|
62
|
+
from = next;
|
|
63
|
+
if (next < 0 || next >= this.list.length)
|
|
64
|
+
continue;
|
|
65
|
+
if (!this.list[next].disabled) {
|
|
66
|
+
this.land(next);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=SelectionModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionModel.js","sourceRoot":"","sources":["../../../src/two-d/ui/SelectionModel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Theme } from './theme.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The text options `Label` and `BitmapLabel` share. Each one extends this with whatever its
|
|
4
|
+
* own renderer needs (`Label` adds stroke/resolution/roundPixels); the shared fields stay in
|
|
5
|
+
* one place so the two labels cannot drift apart on what "themed text" accepts.
|
|
6
|
+
*/
|
|
7
|
+
export interface ThemedTextOptions {
|
|
8
|
+
text?: string;
|
|
9
|
+
color?: number;
|
|
10
|
+
size?: number;
|
|
11
|
+
/** wraps at this width in pixels; omit for a single unwrapped line */
|
|
12
|
+
wrapWidth?: number;
|
|
13
|
+
align?: 'left' | 'center' | 'right';
|
|
14
|
+
bold?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** accepts the `options | string` shorthand both labels offer in their constructors */
|
|
17
|
+
export declare function normalizeTextOptions<T extends ThemedTextOptions>(options: T | string): T;
|
|
18
|
+
/** the align a label uses when the game gave none: the theme direction, not a fixed left */
|
|
19
|
+
export declare function themedAlign(opts: ThemedTextOptions, t: Theme): 'left' | 'center' | 'right';
|
|
20
|
+
//# sourceMappingURL=themedText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themedText.d.ts","sourceRoot":"","sources":["../../../src/two-d/ui/themedText.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,iBAAiB,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,GAAG,CAAC,CAExF;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAE1F"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** accepts the `options | string` shorthand both labels offer in their constructors */
|
|
2
|
+
export function normalizeTextOptions(options) {
|
|
3
|
+
return typeof options === 'string' ? ({ text: options }) : options;
|
|
4
|
+
}
|
|
5
|
+
/** the align a label uses when the game gave none: the theme direction, not a fixed left */
|
|
6
|
+
export function themedAlign(opts, t) {
|
|
7
|
+
return opts.align ?? (t.direction === 'rtl' ? 'right' : 'left');
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=themedText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themedText.js","sourceRoot":"","sources":["../../../src/two-d/ui/themedText.ts"],"names":[],"mappings":"AAiBA,uFAAuF;AACvF,MAAM,UAAU,oBAAoB,CAA8B,OAAmB;IACpF,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACzE,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,IAAuB,EAAE,CAAQ;IAC5D,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* this from `package.json` automatically, chosen because this project's own build already
|
|
10
10
|
* has no step before `tsc`/`vite` that this would need to run ahead of.
|
|
11
11
|
*/
|
|
12
|
-
export declare const version = "0.4.
|
|
12
|
+
export declare const version = "0.4.3";
|
|
13
13
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* this from `package.json` automatically, chosen because this project's own build already
|
|
10
10
|
* has no step before `tsc`/`vite` that this would need to run ahead of.
|
|
11
11
|
*/
|
|
12
|
-
export const version = '0.4.
|
|
12
|
+
export const version = '0.4.3';
|
|
13
13
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datamoc/mw_games",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
41
|
"tools/compile-resources.mjs",
|
|
42
|
+
"tools/compress-dist.mjs",
|
|
43
|
+
"tools/compress-dist.d.mts",
|
|
42
44
|
"LICENSE",
|
|
43
45
|
"README.md"
|
|
44
46
|
],
|
|
@@ -104,6 +106,7 @@
|
|
|
104
106
|
"import": "./dist/simulation/index.js"
|
|
105
107
|
},
|
|
106
108
|
"./tools/compile-resources": "./tools/compile-resources.mjs",
|
|
109
|
+
"./tools/compress-dist": "./tools/compress-dist.mjs",
|
|
107
110
|
"./two-d": {
|
|
108
111
|
"types": "./dist/two-d/index.d.ts",
|
|
109
112
|
"import": "./dist/two-d/index.js"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CompressOptions {
|
|
2
|
+
gzip?: boolean;
|
|
3
|
+
brotli?: boolean;
|
|
4
|
+
xz?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CompressRow {
|
|
8
|
+
file: string;
|
|
9
|
+
raw: number;
|
|
10
|
+
gzip: number;
|
|
11
|
+
brotli: number;
|
|
12
|
+
xz: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function hasXz(): boolean;
|
|
16
|
+
export function compressDist(dir: string, options?: CompressOptions): Promise<CompressRow[]>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { relative, extname, basename } from 'node:path';
|
|
3
|
+
import { gzipSync, brotliCompressSync, constants } from 'node:zlib';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { walk } from './compile-resources.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Writes precompressed siblings (`.gz`, `.br`, optionally `.xz`) next to a build's
|
|
9
|
+
* text files, for servers that can serve them with a `Content-Encoding` header.
|
|
10
|
+
*
|
|
11
|
+
* The originals stay exactly where they are, so a page opened from `file://` keeps
|
|
12
|
+
* working with no server: the compressed files are only ever picked up when a server
|
|
13
|
+
* negotiates them (nginx `gzip_static`, Apache `mod_rewrite`, a CDN, Capacitor live
|
|
14
|
+
* reload, and so on). Nothing in the page references them.
|
|
15
|
+
*
|
|
16
|
+
* Only Node built-ins are used, so this adds no dependency. Gzip (level 9) is the
|
|
17
|
+
* universal fallback every server understands; brotli (quality 11) is smaller and served
|
|
18
|
+
* by preference where the client offers `br`. LZMA2 (`.xz`, via the system `xz` binary
|
|
19
|
+
* when present, opt-in with `--xz`) compresses a little further, but browsers do not
|
|
20
|
+
* decode `xz` as a `Content-Encoding`, so it is an offline archive format, not something
|
|
21
|
+
* a server can negotiate: `gz` stays the fallback.
|
|
22
|
+
*
|
|
23
|
+
* usage: node tools/compress-dist.mjs <dist folder> [--xz] [--no-gzip] [--no-brotli]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const TEXT_EXTENSIONS = new Set(['.js', '.html', '.css', '.svg', '.json', '.txt', '.map']);
|
|
27
|
+
const ALREADY_COMPRESSED = new Set(['.gz', '.br', '.xz', '.png', '.jpg', '.jpeg', '.webp', '.gif', '.wav', '.ogg', '.mp3', '.ttf', '.woff2']);
|
|
28
|
+
const MIN_BYTES = 1024;
|
|
29
|
+
|
|
30
|
+
/** true when the system `xz` binary (LZMA2) is available for the opt-in archive pass */
|
|
31
|
+
export function hasXz() {
|
|
32
|
+
try {
|
|
33
|
+
const found = spawnSync('xz', ['--version'], { stdio: 'ignore' });
|
|
34
|
+
return found.status === 0;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function compressXz(data) {
|
|
41
|
+
const out = spawnSync('xz', ['-c', '-9'], { input: data });
|
|
42
|
+
if (out.status !== 0 || !out.stdout) return undefined;
|
|
43
|
+
return out.stdout;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param dir folder to compress, normally an example's `dist`
|
|
48
|
+
* @param options.gzip write `.gz` siblings (default true)
|
|
49
|
+
* @param options.brotli write `.br` siblings (default true)
|
|
50
|
+
* @param options.xz also write `.xz` siblings where smaller (default false, needs `xz`)
|
|
51
|
+
* @returns one row per file considered, with the sizes actually written
|
|
52
|
+
*/
|
|
53
|
+
export async function compressDist(dir, { gzip = true, brotli = true, xz = false } = {}) {
|
|
54
|
+
const rows = [];
|
|
55
|
+
const wantXz = xz && hasXz();
|
|
56
|
+
for await (const file of walk(dir)) {
|
|
57
|
+
const ext = extname(file).toLowerCase();
|
|
58
|
+
if (!TEXT_EXTENSIONS.has(ext) || ALREADY_COMPRESSED.has(ext)) continue;
|
|
59
|
+
const data = await readFile(file);
|
|
60
|
+
if (data.length < MIN_BYTES) continue;
|
|
61
|
+
const name = relative(dir, file);
|
|
62
|
+
const row = { file: name, raw: data.length, gzip: 0, brotli: 0, xz: 0 };
|
|
63
|
+
if (gzip) {
|
|
64
|
+
const zipped = gzipSync(data, { level: 9 });
|
|
65
|
+
if (zipped.length < data.length) {
|
|
66
|
+
await writeFile(file + '.gz', zipped);
|
|
67
|
+
row.gzip = zipped.length;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (brotli) {
|
|
71
|
+
const squeezed = brotliCompressSync(data, {
|
|
72
|
+
params: { [constants.BROTLI_PARAM_QUALITY]: 11 },
|
|
73
|
+
});
|
|
74
|
+
if (squeezed.length < data.length) {
|
|
75
|
+
await writeFile(file + '.br', squeezed);
|
|
76
|
+
row.brotli = squeezed.length;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (wantXz) {
|
|
80
|
+
const archived = compressXz(data);
|
|
81
|
+
if (archived && archived.length < data.length) {
|
|
82
|
+
await writeFile(file + '.xz', archived);
|
|
83
|
+
row.xz = archived.length;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
rows.push(row);
|
|
87
|
+
}
|
|
88
|
+
return rows;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isMain = process.argv[1] && import.meta.url.endsWith(basename(process.argv[1]));
|
|
92
|
+
|
|
93
|
+
if (isMain) {
|
|
94
|
+
const [dir, ...flags] = process.argv.slice(2);
|
|
95
|
+
if (!dir) {
|
|
96
|
+
console.error('usage: node tools/compress-dist.mjs <dist folder> [--xz] [--no-gzip] [--no-brotli]');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const rows = await compressDist(dir, {
|
|
100
|
+
gzip: !flags.includes('--no-gzip'),
|
|
101
|
+
brotli: !flags.includes('--no-brotli'),
|
|
102
|
+
xz: flags.includes('--xz'),
|
|
103
|
+
});
|
|
104
|
+
const kb = (n) => (n / 1024).toFixed(1).padStart(8) + ' KB';
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
const parts = [`${row.file.padEnd(36)}${kb(row.raw)}`];
|
|
107
|
+
if (row.gzip) parts.push(`gz:${kb(row.gzip)}`);
|
|
108
|
+
if (row.brotli) parts.push(`br:${kb(row.brotli)}`);
|
|
109
|
+
if (row.xz) parts.push(`xz:${kb(row.xz)}`);
|
|
110
|
+
console.log(' ' + parts.join(' '));
|
|
111
|
+
}
|
|
112
|
+
if (flags.includes('--xz') && !hasXz()) {
|
|
113
|
+
console.log(' (xz requested but no `xz` binary found - skipping LZMA2, gz stays the fallback)');
|
|
114
|
+
}
|
|
115
|
+
console.log(` ${rows.length} file(s) considered in ${relative(process.cwd(), dir)}`);
|
|
116
|
+
}
|