@waica/cli 0.3.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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/cli.js +184 -0
- package/dist/editor/assets/abap-08VXUWAP.js +1 -0
- package/dist/editor/assets/apex-BWPQTe0t.js +1 -0
- package/dist/editor/assets/azcli-Bc_sGQ0U.js +1 -0
- package/dist/editor/assets/bat-i0X4ZdIN.js +1 -0
- package/dist/editor/assets/bicep-B5-_aFwp.js +2 -0
- package/dist/editor/assets/cameligo-DMUM7wLl.js +1 -0
- package/dist/editor/assets/clojure-Cm7r79vr.js +1 -0
- package/dist/editor/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/editor/assets/coffee-Ba7i2nA0.js +1 -0
- package/dist/editor/assets/cpp-C7h46wYY.js +1 -0
- package/dist/editor/assets/csharp-BKxtCVv1.js +1 -0
- package/dist/editor/assets/csp-bTuwJoIa.js +1 -0
- package/dist/editor/assets/css-DIMkf-bt.js +3 -0
- package/dist/editor/assets/css.worker-Fwwr_HdT.js +88 -0
- package/dist/editor/assets/cssMode-CQE-WdwL.js +4 -0
- package/dist/editor/assets/cypher-CVaqCwHa.js +1 -0
- package/dist/editor/assets/dart-onAF5SnQ.js +1 -0
- package/dist/editor/assets/dockerfile-DZFCIeNp.js +1 -0
- package/dist/editor/assets/ecl-D05T4iGw.js +1 -0
- package/dist/editor/assets/editor.worker-DsBDrWS5.js +12 -0
- package/dist/editor/assets/elixir-6RTg0lbw.js +1 -0
- package/dist/editor/assets/flow9-C5_-GSwl.js +1 -0
- package/dist/editor/assets/freemarker2-CYgdVabn.js +3 -0
- package/dist/editor/assets/fsharp-C8Ef5oNN.js +1 -0
- package/dist/editor/assets/go-C-y9NEjX.js +1 -0
- package/dist/editor/assets/graphql-fmXr3nnJ.js +1 -0
- package/dist/editor/assets/handlebars-DdeORCJD.js +1 -0
- package/dist/editor/assets/hcl-CpzslTdj.js +1 -0
- package/dist/editor/assets/html-DTIAGV84.js +1 -0
- package/dist/editor/assets/html.worker-B3Ztmmz2.js +495 -0
- package/dist/editor/assets/htmlMode-CRV3iBih.js +4 -0
- package/dist/editor/assets/index-Btzc6zxr.css +1 -0
- package/dist/editor/assets/index-Dhkqk31v.js +11016 -0
- package/dist/editor/assets/ini-sBoK_t0W.js +1 -0
- package/dist/editor/assets/java-BEtHBSE6.js +1 -0
- package/dist/editor/assets/javascript-dxBW86cl.js +1 -0
- package/dist/editor/assets/json.worker-PWQxM7ZO.js +51 -0
- package/dist/editor/assets/jsonMode-DFA5-FFK.js +10 -0
- package/dist/editor/assets/julia-Bri6UV-V.js +1 -0
- package/dist/editor/assets/kotlin-B0qVs-E9.js +1 -0
- package/dist/editor/assets/less-B9JPFI3C.js +2 -0
- package/dist/editor/assets/lexon-CfSJPG6W.js +1 -0
- package/dist/editor/assets/liquid-CScWaXYV.js +1 -0
- package/dist/editor/assets/lua-CsQS60Ue.js +1 -0
- package/dist/editor/assets/m3-D-oSqn_W.js +1 -0
- package/dist/editor/assets/markdown-Cimd5fb3.js +1 -0
- package/dist/editor/assets/mdx-Bvp84SqA.js +1 -0
- package/dist/editor/assets/mips-CIPQ_RoX.js +1 -0
- package/dist/editor/assets/msdax-DauUninz.js +1 -0
- package/dist/editor/assets/mysql-SOo6toE5.js +1 -0
- package/dist/editor/assets/objective-c-FvmIjYaQ.js +1 -0
- package/dist/editor/assets/pascal-DrH0SRf2.js +1 -0
- package/dist/editor/assets/pascaligo-D-ptJ9y-.js +1 -0
- package/dist/editor/assets/perl-oz_6vUea.js +1 -0
- package/dist/editor/assets/pgsql-DTj74zXo.js +1 -0
- package/dist/editor/assets/php-nr791fC2.js +1 -0
- package/dist/editor/assets/pla-CopQ2nXW.js +1 -0
- package/dist/editor/assets/postiats-43DmfD33.js +1 -0
- package/dist/editor/assets/powerquery-D3hlyOfw.js +1 -0
- package/dist/editor/assets/powershell-DmHpPYUd.js +1 -0
- package/dist/editor/assets/protobuf-C531GsRP.js +2 -0
- package/dist/editor/assets/pug-Z5eAx3Zn.js +1 -0
- package/dist/editor/assets/python-CG-OtvFa.js +1 -0
- package/dist/editor/assets/qsharp-DkqhCAOL.js +1 -0
- package/dist/editor/assets/r-BwWrilGY.js +1 -0
- package/dist/editor/assets/razor-r-iUEZPj.js +1 -0
- package/dist/editor/assets/redis-ClamHrr6.js +1 -0
- package/dist/editor/assets/redshift-DT7zqm-g.js +1 -0
- package/dist/editor/assets/restructuredtext-BYgofb2h.js +1 -0
- package/dist/editor/assets/ruby-DezsRK8O.js +1 -0
- package/dist/editor/assets/rust-DdL9SqIa.js +1 -0
- package/dist/editor/assets/sb-CcwsVR0C.js +1 -0
- package/dist/editor/assets/scala-DHpiXF5c.js +1 -0
- package/dist/editor/assets/scheme-BeGwcela.js +1 -0
- package/dist/editor/assets/scss-gp-XZpBa.js +3 -0
- package/dist/editor/assets/shell-CC2rA5mh.js +1 -0
- package/dist/editor/assets/solidity-BEEn4gHE.js +1 -0
- package/dist/editor/assets/sophia-CRfGWb83.js +1 -0
- package/dist/editor/assets/sparql-D_Lu-MrJ.js +1 -0
- package/dist/editor/assets/sql-NEE52Syq.js +1 -0
- package/dist/editor/assets/st-DbInun42.js +1 -0
- package/dist/editor/assets/swift-Bxkupp3x.js +1 -0
- package/dist/editor/assets/systemverilog-Bz4Y3fRF.js +1 -0
- package/dist/editor/assets/tcl-DISqw1ZD.js +1 -0
- package/dist/editor/assets/ts.worker-0mEyHVru.js +51339 -0
- package/dist/editor/assets/tsMode-Bth1rJwM.js +11 -0
- package/dist/editor/assets/twig-De2hgUGE.js +1 -0
- package/dist/editor/assets/typescript-tNRuHsoe.js +1 -0
- package/dist/editor/assets/typespec-B8J7ngcE.js +1 -0
- package/dist/editor/assets/vb-DV3o63ZY.js +1 -0
- package/dist/editor/assets/wgsl-DpFanUEy.js +298 -0
- package/dist/editor/assets/xml-BCx9e6CK.js +1 -0
- package/dist/editor/assets/yaml-Bb-peGP_.js +1 -0
- package/dist/editor/index.html +14 -0
- package/dist/mcp/archetypes.js +103 -0
- package/dist/mcp/cli.js +6 -0
- package/dist/mcp/create-project.js +151 -0
- package/dist/mcp/introspection.js +201 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-coin.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-dog.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-slime.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/art.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/art.js +13 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/bundle.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/bundle.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/controls.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/controls.js +12 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/index.d.ts +38 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/index.js +34 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/manifest.d.ts +15 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/manifest.js +26 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/prefabs.d.ts +7 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/prefabs.js +97 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry-data.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry-data.js +45 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry.d.ts +6 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry.js +15 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/scene-default.d.ts +40 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/scene-default.js +93 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/ui.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/ui.js +24 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/package.json +30 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.d.ts +59 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.js +127 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.d.ts +25 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.js +27 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.d.ts +29 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.js +42 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.d.ts +9 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.js +9 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.js +19 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/npc.d.ts +4 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/npc.js +14 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.d.ts +43 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.js +68 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.d.ts +124 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.js +146 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/player-identity.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/player-identity.js +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/player-states.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/player-states.js +95 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.d.ts +23 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.js +27 -0
- package/dist/mcp/node_modules/@waica/behaviors/package.json +24 -0
- package/dist/mcp/node_modules/@waica/engine/dist/aabb.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/engine/dist/aabb.js +4 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/clip-player.d.ts +20 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/clip-player.js +24 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/contract.d.ts +21 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/contract.js +23 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/sheet.d.ts +65 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/sheet.js +44 -0
- package/dist/mcp/node_modules/@waica/engine/dist/archetype.d.ts +37 -0
- package/dist/mcp/node_modules/@waica/engine/dist/archetype.js +1 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.d.ts +71 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.js +59 -0
- package/dist/mcp/node_modules/@waica/engine/dist/collision-shape.d.ts +26 -0
- package/dist/mcp/node_modules/@waica/engine/dist/collision-shape.js +136 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component-registry.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component-registry.js +44 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component.d.ts +58 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component.js +11 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.d.ts +80 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.js +210 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.d.ts +68 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.js +189 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/hitbox.d.ts +25 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/hitbox.js +21 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/solid.d.ts +32 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/solid.js +45 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.d.ts +51 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.js +112 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.d.ts +22 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.js +52 -0
- package/dist/mcp/node_modules/@waica/engine/dist/events.d.ts +8 -0
- package/dist/mcp/node_modules/@waica/engine/dist/events.js +20 -0
- package/dist/mcp/node_modules/@waica/engine/dist/game.d.ts +95 -0
- package/dist/mcp/node_modules/@waica/engine/dist/game.js +263 -0
- package/dist/mcp/node_modules/@waica/engine/dist/index.d.ts +39 -0
- package/dist/mcp/node_modules/@waica/engine/dist/index.js +26 -0
- package/dist/mcp/node_modules/@waica/engine/dist/input.d.ts +40 -0
- package/dist/mcp/node_modules/@waica/engine/dist/input.js +84 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.d.ts +80 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.js +93 -0
- package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.d.ts +21 -0
- package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.js +77 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.d.ts +90 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.js +81 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.d.ts +83 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.js +173 -0
- package/dist/mcp/node_modules/@waica/engine/dist/stats.d.ts +27 -0
- package/dist/mcp/node_modules/@waica/engine/dist/stats.js +54 -0
- package/dist/mcp/node_modules/@waica/engine/dist/ui.d.ts +47 -0
- package/dist/mcp/node_modules/@waica/engine/dist/ui.js +175 -0
- package/dist/mcp/node_modules/@waica/engine/package.json +25 -0
- package/dist/mcp/package-resolver.js +240 -0
- package/dist/mcp/project-path.js +86 -0
- package/dist/mcp/scaffolds.js +153 -0
- package/dist/mcp/server.js +217 -0
- package/dist/mcp/stdio.js +13 -0
- package/dist/mcp/template/README.md +22 -0
- package/dist/mcp/template/_gitignore +5 -0
- package/dist/mcp/template/index.html +25 -0
- package/dist/mcp/template/package.json.tpl +20 -0
- package/dist/mcp/template/public/waica.params.json +1 -0
- package/dist/mcp/template/src/art/waica-coin.png +0 -0
- package/dist/mcp/template/src/art/waica-dog.png +0 -0
- package/dist/mcp/template/src/art/waica-slime.png +0 -0
- package/dist/mcp/template/src/characters/player.character.json +133 -0
- package/dist/mcp/template/src/characters/slime.character.json +61 -0
- package/dist/mcp/template/src/controls.json +8 -0
- package/dist/mcp/template/src/game.json +10 -0
- package/dist/mcp/template/src/main.ts +105 -0
- package/dist/mcp/template/src/objects/coin.object.json +41 -0
- package/dist/mcp/template/src/scenes/main.scene.json +217 -0
- package/dist/mcp/template/src/stats.json +7 -0
- package/dist/mcp/template/src/tiles/block.tile.json +21 -0
- package/dist/mcp/template/src/tiles/decor.tile.json +14 -0
- package/dist/mcp/template/src/tiles/platform.tile.json +21 -0
- package/dist/mcp/template/src/ui/coin-counter.html +12 -0
- package/dist/mcp/template/tsconfig.json +15 -0
- package/dist/mcp/template/vite.config.ts +3 -0
- package/dist/mcp/validation.js +323 -0
- package/dist/mcp/workspace-loader.js +14 -0
- package/dist/mcp/workspace-runtime.js +109 -0
- package/dist/server.js +210 -0
- package/package.json +38 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Neutral engine baseline; archetypes own their action vocabulary. */
|
|
2
|
+
export const DEFAULT_BINDINGS = {};
|
|
3
|
+
/**
|
|
4
|
+
* Action-based input with archetype default bindings.
|
|
5
|
+
* v0: keyboard. TODO(H1): gamepad and touch.
|
|
6
|
+
*/
|
|
7
|
+
export class Input {
|
|
8
|
+
bindings = new Map();
|
|
9
|
+
down = new Set();
|
|
10
|
+
justDown = new Set();
|
|
11
|
+
used = new Set();
|
|
12
|
+
/** Installs exactly the action map supplied by the active archetype/project. */
|
|
13
|
+
constructor(bindings = DEFAULT_BINDINGS) {
|
|
14
|
+
for (const [action, codes] of Object.entries(bindings)) {
|
|
15
|
+
this.bindings.set(action, new Set(codes));
|
|
16
|
+
}
|
|
17
|
+
window.addEventListener('keydown', this.onKeyDown);
|
|
18
|
+
window.addEventListener('keyup', this.onKeyUp);
|
|
19
|
+
window.addEventListener('blur', this.releaseAll);
|
|
20
|
+
document.addEventListener('visibilitychange', this.onVisibilityChange);
|
|
21
|
+
}
|
|
22
|
+
/** Is the action held this frame? */
|
|
23
|
+
held(action) {
|
|
24
|
+
return this.isActive(action, this.down);
|
|
25
|
+
}
|
|
26
|
+
/** Was the action pressed exactly this frame? */
|
|
27
|
+
justPressed(action) {
|
|
28
|
+
return this.isActive(action, this.justDown);
|
|
29
|
+
}
|
|
30
|
+
/** -1..1 axis from two actions (left/right by default). */
|
|
31
|
+
axis(negative = 'left', positive = 'right') {
|
|
32
|
+
return (this.held(positive) ? 1 : 0) - (this.held(negative) ? 1 : 0);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Marks this frame's press of the action as spent, so consumers that
|
|
36
|
+
* honor consumption (state-machine 'input:' triggers) ignore it. One
|
|
37
|
+
* press does one thing: the press that launches a ground jump can't
|
|
38
|
+
* also fire a "key press jump" transition on the very same frame.
|
|
39
|
+
*/
|
|
40
|
+
consume(action) {
|
|
41
|
+
this.used.add(action);
|
|
42
|
+
}
|
|
43
|
+
/** Was this frame's press already spent by someone? */
|
|
44
|
+
consumed(action) {
|
|
45
|
+
return this.used.has(action);
|
|
46
|
+
}
|
|
47
|
+
/** Called by the Game at the end of each frame. */
|
|
48
|
+
endFrame() {
|
|
49
|
+
this.justDown.clear();
|
|
50
|
+
this.used.clear();
|
|
51
|
+
}
|
|
52
|
+
dispose() {
|
|
53
|
+
window.removeEventListener('keydown', this.onKeyDown);
|
|
54
|
+
window.removeEventListener('keyup', this.onKeyUp);
|
|
55
|
+
window.removeEventListener('blur', this.releaseAll);
|
|
56
|
+
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
|
57
|
+
}
|
|
58
|
+
isActive(action, set) {
|
|
59
|
+
const codes = this.bindings.get(action);
|
|
60
|
+
if (!codes)
|
|
61
|
+
return false;
|
|
62
|
+
for (const code of codes)
|
|
63
|
+
if (set.has(code))
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
releaseAll = () => {
|
|
68
|
+
this.down.clear();
|
|
69
|
+
this.justDown.clear();
|
|
70
|
+
};
|
|
71
|
+
onVisibilityChange = () => {
|
|
72
|
+
if (document.visibilityState === 'hidden')
|
|
73
|
+
this.releaseAll();
|
|
74
|
+
};
|
|
75
|
+
onKeyDown = (e) => {
|
|
76
|
+
if (e.repeat)
|
|
77
|
+
return;
|
|
78
|
+
this.down.add(e.code);
|
|
79
|
+
this.justDown.add(e.code);
|
|
80
|
+
};
|
|
81
|
+
onKeyUp = (e) => {
|
|
82
|
+
this.down.delete(e.code);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { SceneCameraJson } from './camera.js';
|
|
2
|
+
import type { ComponentClass } from './component.js';
|
|
3
|
+
import type { Entity } from './entity.js';
|
|
4
|
+
import type { Game } from './game.js';
|
|
5
|
+
/**
|
|
6
|
+
* Waica's scene format: declarative, git-friendly data, editable by the
|
|
7
|
+
* visual editor. The scene is the source of truth; the Game is its live
|
|
8
|
+
* projection.
|
|
9
|
+
*/
|
|
10
|
+
export interface SceneComponentJson {
|
|
11
|
+
type: string;
|
|
12
|
+
props?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface SceneEntityJson {
|
|
15
|
+
name: string;
|
|
16
|
+
position?: [number, number];
|
|
17
|
+
/** Prefab ref like "characters/slime", resolved against SceneRegistry.prefabs. */
|
|
18
|
+
prefab?: string;
|
|
19
|
+
/** Per-component prop overrides on top of the prefab: componentType -> propName -> value. */
|
|
20
|
+
overrides?: Record<string, Record<string, unknown>>;
|
|
21
|
+
components?: SceneComponentJson[];
|
|
22
|
+
/** Editor-only grouping label; spawning ignores it. */
|
|
23
|
+
folder?: string;
|
|
24
|
+
}
|
|
25
|
+
/** A reusable entity template: a typed bag of components a scene can reference. */
|
|
26
|
+
export interface PrefabJson {
|
|
27
|
+
waicaPrefab: 1;
|
|
28
|
+
type: 'character' | 'object' | 'tile';
|
|
29
|
+
components: SceneComponentJson[];
|
|
30
|
+
}
|
|
31
|
+
export interface SceneJson {
|
|
32
|
+
waicaScene: 1 | 2 | 3;
|
|
33
|
+
/** The scene's built-in camera (v3); absent = the host keeps control. */
|
|
34
|
+
camera?: SceneCameraJson;
|
|
35
|
+
entities: SceneEntityJson[];
|
|
36
|
+
/** UI pieces (src/ui/*.html) mounted visible when the scene loads. */
|
|
37
|
+
ui?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Editor-only folder registry: keeps empty folders alive and ordered.
|
|
40
|
+
* Folders group entities in the editor's explorer; the runtime ignores them.
|
|
41
|
+
*/
|
|
42
|
+
folders?: string[];
|
|
43
|
+
}
|
|
44
|
+
/** Which components exist and how to resolve archetype assets (waica:*). */
|
|
45
|
+
export interface SceneRegistry {
|
|
46
|
+
components: Record<string, ComponentClass>;
|
|
47
|
+
/**
|
|
48
|
+
* Resolves asset URIs — "waica:dog" registry keys or project paths like
|
|
49
|
+
* "src/art/hero.png" — to loadable URLs. MUST return unknown strings
|
|
50
|
+
* unchanged: every string prop goes through it.
|
|
51
|
+
*/
|
|
52
|
+
resolveAsset?: (uri: string) => string;
|
|
53
|
+
/** Prefab definitions keyed by ref ("characters/slime"). */
|
|
54
|
+
prefabs?: Record<string, PrefabJson>;
|
|
55
|
+
/** UI piece sources keyed by name ("coin-counter" → its HTML). */
|
|
56
|
+
ui?: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Own-property lookup. Scene JSON is data — a component or prefab named
|
|
60
|
+
* "constructor" or "toString" must read as missing, not as whatever sits
|
|
61
|
+
* on Object.prototype.
|
|
62
|
+
*/
|
|
63
|
+
export declare function registryEntry<T>(record: Record<string, T> | undefined, key: string): T | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Runs every string prop through the registry's asset resolver (if any),
|
|
66
|
+
* recursing into arrays and plain objects — nested textures (e.g. an
|
|
67
|
+
* AnimatedSprite's extraSheets) resolve like top-level ones. Safe because
|
|
68
|
+
* resolvers return unknown strings unchanged.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveProps(props: Record<string, unknown> | undefined, registry: SceneRegistry): Record<string, unknown>;
|
|
71
|
+
/**
|
|
72
|
+
* Expands an entity's prefab (with its overrides) into the final component
|
|
73
|
+
* list: prefab components first, inline extras appended after. Pure — inputs
|
|
74
|
+
* are never mutated; merged components are fresh objects.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveEntityComponents(entity: SceneEntityJson, prefabs?: Record<string, PrefabJson>): SceneComponentJson[];
|
|
77
|
+
/** Instantiates a scene entity into the game. */
|
|
78
|
+
export declare function spawnFromJson(game: Game, json: SceneEntityJson, registry: SceneRegistry): Entity;
|
|
79
|
+
/** Loads a full scene into the game. */
|
|
80
|
+
export declare function loadScene(game: Game, scene: SceneJson, registry: SceneRegistry): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own-property lookup. Scene JSON is data — a component or prefab named
|
|
3
|
+
* "constructor" or "toString" must read as missing, not as whatever sits
|
|
4
|
+
* on Object.prototype.
|
|
5
|
+
*/
|
|
6
|
+
export function registryEntry(record, key) {
|
|
7
|
+
if (!record || !Object.prototype.hasOwnProperty.call(record, key))
|
|
8
|
+
return undefined;
|
|
9
|
+
return record[key];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs every string prop through the registry's asset resolver (if any),
|
|
13
|
+
* recursing into arrays and plain objects — nested textures (e.g. an
|
|
14
|
+
* AnimatedSprite's extraSheets) resolve like top-level ones. Safe because
|
|
15
|
+
* resolvers return unknown strings unchanged.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveProps(props, registry) {
|
|
18
|
+
if (!props)
|
|
19
|
+
return {};
|
|
20
|
+
const resolve = registry.resolveAsset;
|
|
21
|
+
if (!resolve)
|
|
22
|
+
return { ...props };
|
|
23
|
+
const walk = (value) => {
|
|
24
|
+
if (typeof value === 'string')
|
|
25
|
+
return resolve(value);
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(walk);
|
|
28
|
+
if (value && typeof value === 'object' && value.constructor === Object) {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [key, entry] of Object.entries(value))
|
|
31
|
+
out[key] = walk(entry);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
return walk(props);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Expands an entity's prefab (with its overrides) into the final component
|
|
40
|
+
* list: prefab components first, inline extras appended after. Pure — inputs
|
|
41
|
+
* are never mutated; merged components are fresh objects.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveEntityComponents(entity, prefabs) {
|
|
44
|
+
const inline = entity.components ?? [];
|
|
45
|
+
if (!entity.prefab)
|
|
46
|
+
return inline;
|
|
47
|
+
const prefab = registryEntry(prefabs, entity.prefab);
|
|
48
|
+
if (!prefab) {
|
|
49
|
+
console.warn(`[waica] unknown prefab in scene: "${entity.prefab}" (${entity.name})`);
|
|
50
|
+
return inline;
|
|
51
|
+
}
|
|
52
|
+
const fromPrefab = prefab.components.map((comp) => ({
|
|
53
|
+
type: comp.type,
|
|
54
|
+
props: { ...comp.props, ...entity.overrides?.[comp.type] },
|
|
55
|
+
}));
|
|
56
|
+
return [...fromPrefab, ...inline];
|
|
57
|
+
}
|
|
58
|
+
/** Instantiates a scene entity into the game. */
|
|
59
|
+
export function spawnFromJson(game, json, registry) {
|
|
60
|
+
const entity = game.spawn(json.name);
|
|
61
|
+
if (json.position)
|
|
62
|
+
entity.position.set(json.position[0], json.position[1], 0);
|
|
63
|
+
for (const comp of resolveEntityComponents(json, registry.prefabs)) {
|
|
64
|
+
const Class = registryEntry(registry.components, comp.type);
|
|
65
|
+
if (!Class) {
|
|
66
|
+
console.warn(`[waica] unknown component in scene: "${comp.type}" (${json.name})`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// The registry now carries project-owned classes, i.e. arbitrary user
|
|
70
|
+
// code. A throwing constructor or onReady costs that one component —
|
|
71
|
+
// never the rest of the scene, and never the editor hosting it.
|
|
72
|
+
try {
|
|
73
|
+
entity.add(Class, resolveProps(comp.props, registry));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
console.error(`[waica] component "${comp.type}" failed on "${json.name}": ${message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return entity;
|
|
81
|
+
}
|
|
82
|
+
/** Loads a full scene into the game. */
|
|
83
|
+
export function loadScene(game, scene, registry) {
|
|
84
|
+
game.registry = registry;
|
|
85
|
+
for (const entityJson of scene.entities)
|
|
86
|
+
spawnFromJson(game, entityJson, registry);
|
|
87
|
+
// After the spawns: with a follow target, the camera starts centered on it.
|
|
88
|
+
game.setSceneCamera(scene.camera);
|
|
89
|
+
if (registry.ui)
|
|
90
|
+
game.ui.defineAll(registry.ui);
|
|
91
|
+
for (const name of scene.ui ?? [])
|
|
92
|
+
game.ui.show(name);
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CollisionBody } from './collision-shape.js';
|
|
2
|
+
import { Solid } from './components/solid.js';
|
|
3
|
+
import type { Entity } from './entity.js';
|
|
4
|
+
export type CollisionAxis = 'x' | 'y';
|
|
5
|
+
export interface SolidAxisOptions {
|
|
6
|
+
/** Dynamic entity whose axis position has already been moved to its target. */
|
|
7
|
+
entity: Entity;
|
|
8
|
+
axis: CollisionAxis;
|
|
9
|
+
/** Known pre-move position on this axis. */
|
|
10
|
+
previous: number;
|
|
11
|
+
/** Current world-space collision body; read again as the axis position changes. */
|
|
12
|
+
body(): CollisionBody;
|
|
13
|
+
/** Receives each nearest Solid that blocks this axis move. */
|
|
14
|
+
onContact?(solid: Solid): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves one axis against scene Solids. Large displacements are split into
|
|
18
|
+
* body-sized steps so a thin wall cannot sit entirely between two samples.
|
|
19
|
+
* Returns whether a new contact blocked the move.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSolidAxis({ entity, axis, previous, body, onContact, }: SolidAxisOptions): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { collisionBounds, collisionOverlap } from './collision-shape.js';
|
|
2
|
+
import { Solid } from './components/solid.js';
|
|
3
|
+
const CONTACT_TOLERANCE = 1e-7;
|
|
4
|
+
function bodyFor(solid) {
|
|
5
|
+
return {
|
|
6
|
+
x: solid.entity.position.x + solid.offsetX,
|
|
7
|
+
y: solid.entity.position.y + solid.offsetY,
|
|
8
|
+
width: solid.width,
|
|
9
|
+
height: solid.height,
|
|
10
|
+
shape: solid.shape,
|
|
11
|
+
points: solid.points,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves one axis against scene Solids. Large displacements are split into
|
|
16
|
+
* body-sized steps so a thin wall cannot sit entirely between two samples.
|
|
17
|
+
* Returns whether a new contact blocked the move.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveSolidAxis({ entity, axis, previous, body, onContact, }) {
|
|
20
|
+
const position = entity.position;
|
|
21
|
+
const target = position[axis];
|
|
22
|
+
const solids = entity.game.entities
|
|
23
|
+
.filter((other) => other !== entity)
|
|
24
|
+
.map((other) => other.get(Solid))
|
|
25
|
+
.filter((solid) => solid !== undefined);
|
|
26
|
+
position[axis] = previous;
|
|
27
|
+
// Preserve the established spawn-inside-wall bail: pre-existing overlaps
|
|
28
|
+
// are ignored for this move rather than becoming arbitrary push-out rules.
|
|
29
|
+
const ignored = new Set(solids.filter((solid) => collisionOverlap(body(), bodyFor(solid))));
|
|
30
|
+
const bounds = collisionBounds(body());
|
|
31
|
+
const extent = axis === 'x' ? bounds.right - bounds.left : bounds.top - bounds.bottom;
|
|
32
|
+
const maxStep = Math.max(extent / 2, 0.05);
|
|
33
|
+
const displacement = target - previous;
|
|
34
|
+
const steps = Math.max(1, Math.ceil(Math.abs(displacement) / maxStep));
|
|
35
|
+
let free = previous;
|
|
36
|
+
for (let step = 1; step <= steps; step++) {
|
|
37
|
+
const candidate = previous + displacement * (step / steps);
|
|
38
|
+
position[axis] = candidate;
|
|
39
|
+
const blockers = solids.filter((solid) => !ignored.has(solid) && collisionOverlap(body(), bodyFor(solid)));
|
|
40
|
+
if (blockers.length === 0) {
|
|
41
|
+
free = candidate;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// More than one Solid can occupy a sample. Resolve against each from the
|
|
45
|
+
// same known-free point and keep the nearest contact along this move.
|
|
46
|
+
let contact = candidate;
|
|
47
|
+
let contactDistance = Infinity;
|
|
48
|
+
let contacts = [];
|
|
49
|
+
for (const solid of blockers) {
|
|
50
|
+
let open = free;
|
|
51
|
+
let blocked = candidate;
|
|
52
|
+
for (let iteration = 0; iteration < 14; iteration++) {
|
|
53
|
+
const middle = (open + blocked) / 2;
|
|
54
|
+
position[axis] = middle;
|
|
55
|
+
if (collisionOverlap(body(), bodyFor(solid)))
|
|
56
|
+
blocked = middle;
|
|
57
|
+
else
|
|
58
|
+
open = middle;
|
|
59
|
+
}
|
|
60
|
+
const distance = Math.abs(open - free);
|
|
61
|
+
if (distance < contactDistance - CONTACT_TOLERANCE) {
|
|
62
|
+
contact = open;
|
|
63
|
+
contactDistance = distance;
|
|
64
|
+
contacts = [solid];
|
|
65
|
+
}
|
|
66
|
+
else if (Math.abs(distance - contactDistance) <= CONTACT_TOLERANCE) {
|
|
67
|
+
contacts.push(solid);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
position[axis] = contact;
|
|
71
|
+
for (const solid of contacts)
|
|
72
|
+
onContact?.(solid);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
position[axis] = target;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Entity } from '../entity.js';
|
|
2
|
+
import type { Game } from '../game.js';
|
|
3
|
+
import type { StateJson, StateMachine } from './state-machine.js';
|
|
4
|
+
/** What state code receives: the entity, the game, and the machine itself. */
|
|
5
|
+
export interface StateContext {
|
|
6
|
+
entity: Entity;
|
|
7
|
+
game: Game;
|
|
8
|
+
fsm: StateMachine;
|
|
9
|
+
}
|
|
10
|
+
/** Code for one state. Every hook is optional — a state can be pure data. */
|
|
11
|
+
export interface StateHooks {
|
|
12
|
+
onEnter?(ctx: StateContext): void;
|
|
13
|
+
onUpdate?(ctx: StateContext, dt: number): void;
|
|
14
|
+
onCollide?(ctx: StateContext, other: Entity): void;
|
|
15
|
+
onExit?(ctx: StateContext): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A named bundle of state code. Two entries are special: '*' is the
|
|
19
|
+
* always-hook — it runs every frame no matter which state is active
|
|
20
|
+
* (per-frame bookkeeping like motor timers lives there) — and 'default'
|
|
21
|
+
* is the fallback — a state that doesn't define a phase inherits it, so
|
|
22
|
+
* a custom state keeps the stock body update unless it overrides it.
|
|
23
|
+
*/
|
|
24
|
+
export type StateLogic = Record<string, StateHooks>;
|
|
25
|
+
/**
|
|
26
|
+
* Registers state code under a logic-set name. A role's name is also its
|
|
27
|
+
* logic-set name, so this is how a role's code is extended: registering
|
|
28
|
+
* again with the same name merges per state.
|
|
29
|
+
*/
|
|
30
|
+
export declare function defineStates(name: string, states: StateLogic): void;
|
|
31
|
+
/** A role's starter state graph — what new characters of the role begin with. */
|
|
32
|
+
export interface RoleGraph {
|
|
33
|
+
initial: string;
|
|
34
|
+
states: Record<string, StateJson>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A character role: the whole behavior package behind one answer to
|
|
38
|
+
* "what is this character?". The editor uses the description to let the
|
|
39
|
+
* role explain itself, and the driver + graph to install the package.
|
|
40
|
+
*/
|
|
41
|
+
export interface RoleDefinition {
|
|
42
|
+
/** One plain-language line: what this role is and does. */
|
|
43
|
+
description: string;
|
|
44
|
+
/**
|
|
45
|
+
* Component the role's states move (they early-return without it).
|
|
46
|
+
* Several roles may share one driver — the contract is N roles : 1 driver.
|
|
47
|
+
*/
|
|
48
|
+
driver?: string;
|
|
49
|
+
/** Starter graph installed when a character adopts this role. */
|
|
50
|
+
graph?: RoleGraph;
|
|
51
|
+
/** The role's state code, registered as the role's logic set. */
|
|
52
|
+
states?: StateLogic;
|
|
53
|
+
/**
|
|
54
|
+
* Signals the role's code emits (name → plain-language description).
|
|
55
|
+
* The editor lists them wherever a transition asks for a signal, so
|
|
56
|
+
* users pick from a vocabulary instead of typing names blind.
|
|
57
|
+
*/
|
|
58
|
+
signals?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/** All state-registry data an archetype installs before project extensions. */
|
|
61
|
+
export interface ArchetypeBundle {
|
|
62
|
+
roles: Readonly<Record<string, RoleDefinition>>;
|
|
63
|
+
/** Extra named logic sets that are not themselves roles. */
|
|
64
|
+
logicSets?: Readonly<Record<string, StateLogic>>;
|
|
65
|
+
}
|
|
66
|
+
/** Clears role definitions and every named logic set. */
|
|
67
|
+
export declare function resetRegistries(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Replaces the active registry contents with one archetype's complete bundle.
|
|
70
|
+
* Project defineRole/defineStates calls may extend this clean baseline after it
|
|
71
|
+
* is installed.
|
|
72
|
+
*/
|
|
73
|
+
export declare function installArchetype(bundle: ArchetypeBundle): void;
|
|
74
|
+
/**
|
|
75
|
+
* Registers a character role. Prefabs adopt it via the StateMachine's
|
|
76
|
+
* `role` prop; the role's name doubles as its logic-set name, so
|
|
77
|
+
* defineStates(name, {...}) extends its code. Registering an existing
|
|
78
|
+
* role again merges its definition (and its states).
|
|
79
|
+
*/
|
|
80
|
+
export declare function defineRole(name: string, def: RoleDefinition): void;
|
|
81
|
+
/** The registered role, if any — driver, graph and description included. */
|
|
82
|
+
export declare function roleDefinition(name: string): RoleDefinition | undefined;
|
|
83
|
+
/** Every registered role name — for the editor's Role pickers. */
|
|
84
|
+
export declare function registeredRoles(): string[];
|
|
85
|
+
/** The registered code for a set, if any. */
|
|
86
|
+
export declare function logicSet(name: string): StateLogic | undefined;
|
|
87
|
+
/** Every registered set name — for error messages and editor pickers. */
|
|
88
|
+
export declare function registeredLogicSets(): string[];
|
|
89
|
+
/** Closest registered set to a (likely typo'd) name, for the not-found error. */
|
|
90
|
+
export declare function closestLogicSet(name: string): string | undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const sets = new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Registers state code under a logic-set name. A role's name is also its
|
|
4
|
+
* logic-set name, so this is how a role's code is extended: registering
|
|
5
|
+
* again with the same name merges per state.
|
|
6
|
+
*/
|
|
7
|
+
export function defineStates(name, states) {
|
|
8
|
+
sets.set(name, { ...sets.get(name), ...states });
|
|
9
|
+
}
|
|
10
|
+
const roles = new Map();
|
|
11
|
+
/** Clears role definitions and every named logic set. */
|
|
12
|
+
export function resetRegistries() {
|
|
13
|
+
roles.clear();
|
|
14
|
+
sets.clear();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Replaces the active registry contents with one archetype's complete bundle.
|
|
18
|
+
* Project defineRole/defineStates calls may extend this clean baseline after it
|
|
19
|
+
* is installed.
|
|
20
|
+
*/
|
|
21
|
+
export function installArchetype(bundle) {
|
|
22
|
+
resetRegistries();
|
|
23
|
+
for (const [name, def] of Object.entries(bundle.roles))
|
|
24
|
+
defineRole(name, def);
|
|
25
|
+
for (const [name, states] of Object.entries(bundle.logicSets ?? {})) {
|
|
26
|
+
defineStates(name, states);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Registers a character role. Prefabs adopt it via the StateMachine's
|
|
31
|
+
* `role` prop; the role's name doubles as its logic-set name, so
|
|
32
|
+
* defineStates(name, {...}) extends its code. Registering an existing
|
|
33
|
+
* role again merges its definition (and its states).
|
|
34
|
+
*/
|
|
35
|
+
export function defineRole(name, def) {
|
|
36
|
+
roles.set(name, { ...roles.get(name), ...def });
|
|
37
|
+
if (def.states)
|
|
38
|
+
defineStates(name, def.states);
|
|
39
|
+
}
|
|
40
|
+
/** The registered role, if any — driver, graph and description included. */
|
|
41
|
+
export function roleDefinition(name) {
|
|
42
|
+
return roles.get(name);
|
|
43
|
+
}
|
|
44
|
+
/** Every registered role name — for the editor's Role pickers. */
|
|
45
|
+
export function registeredRoles() {
|
|
46
|
+
return [...roles.keys()];
|
|
47
|
+
}
|
|
48
|
+
/** The registered code for a set, if any. */
|
|
49
|
+
export function logicSet(name) {
|
|
50
|
+
return sets.get(name);
|
|
51
|
+
}
|
|
52
|
+
/** Every registered set name — for error messages and editor pickers. */
|
|
53
|
+
export function registeredLogicSets() {
|
|
54
|
+
return [...sets.keys()];
|
|
55
|
+
}
|
|
56
|
+
/** Closest registered set to a (likely typo'd) name, for the not-found error. */
|
|
57
|
+
export function closestLogicSet(name) {
|
|
58
|
+
let best;
|
|
59
|
+
let bestDistance = Infinity;
|
|
60
|
+
for (const candidate of sets.keys()) {
|
|
61
|
+
const distance = editDistance(name, candidate);
|
|
62
|
+
if (distance < bestDistance) {
|
|
63
|
+
bestDistance = distance;
|
|
64
|
+
best = candidate;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return bestDistance <= Math.max(2, Math.floor(name.length / 3)) ? best : undefined;
|
|
68
|
+
}
|
|
69
|
+
function editDistance(a, b) {
|
|
70
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
71
|
+
for (let i = 1; i <= a.length; i++) {
|
|
72
|
+
let diagonal = row[0];
|
|
73
|
+
row[0] = i;
|
|
74
|
+
for (let j = 1; j <= b.length; j++) {
|
|
75
|
+
const previous = row[j];
|
|
76
|
+
row[j] = Math.min(previous + 1, row[j - 1] + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
77
|
+
diagonal = previous;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return row[b.length];
|
|
81
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Component } from '../component.js';
|
|
2
|
+
import type { Entity } from '../entity.js';
|
|
3
|
+
import { type StateHooks } from './hooks.js';
|
|
4
|
+
/** One outgoing edge: when `on` fires, the machine moves to `to`. */
|
|
5
|
+
export interface StateTransitionJson {
|
|
6
|
+
/** Trigger: 'input:<action>' | 'timer:<seconds>' | 'signal:<name>'. */
|
|
7
|
+
on: string;
|
|
8
|
+
to: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* One state, as prefab data. Its code (if any) is looked up by state name
|
|
12
|
+
* in the machine's logic set. The '*' state key holds from-any-state
|
|
13
|
+
* transitions instead of a real state.
|
|
14
|
+
*/
|
|
15
|
+
export interface StateJson {
|
|
16
|
+
/** Clip to play on enter; defaults to the state's own name. */
|
|
17
|
+
clip?: string;
|
|
18
|
+
transitions?: StateTransitionJson[];
|
|
19
|
+
}
|
|
20
|
+
/** What a trigger string is checked against. */
|
|
21
|
+
export interface TriggerEnv {
|
|
22
|
+
/** An unconsumed press of the action this frame (see Input.consume). */
|
|
23
|
+
justPressed(action: string): boolean;
|
|
24
|
+
/** Seconds spent in the current state. */
|
|
25
|
+
elapsed: number;
|
|
26
|
+
signals: ReadonlySet<string>;
|
|
27
|
+
}
|
|
28
|
+
/** Whether one trigger fires. Unknown or malformed triggers never fire. */
|
|
29
|
+
export declare function evaluateTrigger(on: string, env: TriggerEnv): boolean;
|
|
30
|
+
/** The first firing transition: the state's own edges, then '*''s. */
|
|
31
|
+
export declare function nextTransition(states: Record<string, StateJson>, current: string, env: TriggerEnv): StateTransitionJson | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The hooks that actually run for one phase of a state: the state's own
|
|
34
|
+
* (logic set + instance), or — when none of them define the phase — the
|
|
35
|
+
* set's 'default' entry. A custom state keeps the role's stock body
|
|
36
|
+
* update unless it brings its own.
|
|
37
|
+
*/
|
|
38
|
+
export declare function phaseHooks(hooks: StateHooks[], fallback: StateHooks | undefined, phase: keyof StateHooks): StateHooks[];
|
|
39
|
+
/**
|
|
40
|
+
* Finite state machine: the single owner of a character's per-frame
|
|
41
|
+
* logic — only the active state's code moves the body, so behaviors
|
|
42
|
+
* never fight over velocity. States and transitions are prefab data;
|
|
43
|
+
* state code lives in the role's logic set (defineRole / defineStates)
|
|
44
|
+
* picked by `role`. Entering a state plays the sibling AnimatedSprite
|
|
45
|
+
* clip of the same name (or the state's `clip` override); a missing
|
|
46
|
+
* clip warns and keeps the previous one.
|
|
47
|
+
*/
|
|
48
|
+
export declare class StateMachine extends Component {
|
|
49
|
+
static componentName: string;
|
|
50
|
+
static displayName: string;
|
|
51
|
+
static params: {
|
|
52
|
+
role: {
|
|
53
|
+
label: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/** The character's role — names the logic set providing its state code. */
|
|
57
|
+
role: string;
|
|
58
|
+
/** Starting state; defaults to the first declared state. */
|
|
59
|
+
initial: string;
|
|
60
|
+
states: Record<string, StateJson>;
|
|
61
|
+
/** Active state name. */
|
|
62
|
+
current: string;
|
|
63
|
+
/** Seconds spent in the active state. */
|
|
64
|
+
elapsed: number;
|
|
65
|
+
private readonly instanceHooks;
|
|
66
|
+
private readonly signals;
|
|
67
|
+
private readonly warnedClips;
|
|
68
|
+
onReady(): void;
|
|
69
|
+
/** Adds instance-level hooks on top of the logic set — the escape hatch. */
|
|
70
|
+
on(state: string, hooks: StateHooks): void;
|
|
71
|
+
/** Queues a signal for 'signal:<name>' transitions, consumed this frame or the next. */
|
|
72
|
+
signal(name: string): void;
|
|
73
|
+
/** Jumps straight to a state, ignoring transitions. */
|
|
74
|
+
goto(state: string): void;
|
|
75
|
+
onUpdate(dt: number): void;
|
|
76
|
+
onCollide(other: Entity): void;
|
|
77
|
+
private env;
|
|
78
|
+
private hooksFor;
|
|
79
|
+
private run;
|
|
80
|
+
private runCollision;
|
|
81
|
+
private enter;
|
|
82
|
+
private playClip;
|
|
83
|
+
}
|