@waica/cli 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/editor/assets/{cssMode-D3SZSKEA.js → cssMode-DwPoqXFO.js} +1 -1
- package/dist/editor/assets/{freemarker2-BH1pa3TU.js → freemarker2-gl5y25bH.js} +1 -1
- package/dist/editor/assets/{handlebars-Cf7meNHo.js → handlebars-DHUb8mb3.js} +1 -1
- package/dist/editor/assets/{html-6hqYzl5P.js → html-C8W-psr4.js} +1 -1
- package/dist/editor/assets/{htmlMode-D_IiyUdi.js → htmlMode-BiF5TH2_.js} +1 -1
- package/dist/editor/assets/{index-Cy5pmBhN.css → index-CzarIYLW.css} +1 -1
- package/dist/editor/assets/{index-CJyB1cBF.js → index-DtMwWp8R.js} +6380 -2495
- package/dist/editor/assets/{javascript-CTeTqLj8.js → javascript-D_t2jyWi.js} +1 -1
- package/dist/editor/assets/{jsonMode-t8AC-38H.js → jsonMode-CZ0SltDj.js} +1 -1
- package/dist/editor/assets/{liquid-Dc6NoKas.js → liquid-DUxURZui.js} +1 -1
- package/dist/editor/assets/{mdx-DCPtX-bs.js → mdx-CDIQ7hmo.js} +1 -1
- package/dist/editor/assets/{python-DXAju_oJ.js → python-Cra2N916.js} +1 -1
- package/dist/editor/assets/{razor-DqQldpv6.js → razor-B3qV-mNp.js} +1 -1
- package/dist/editor/assets/{tsMode-B60EhHlZ.js → tsMode-Ci457z4v.js} +1 -1
- package/dist/editor/assets/{typescript-C4LyYAns.js → typescript-BXjh3GLT.js} +1 -1
- package/dist/editor/assets/{xml-B0QcT5_Z.js → xml-DQuEpqk5.js} +1 -1
- package/dist/editor/assets/{yaml-CEpyO2Qc.js → yaml-CQA7WIT-.js} +1 -1
- package/dist/editor/index.html +2 -2
- package/dist/mcp/archetypes.js +6 -2
- package/dist/mcp/create-project.js +12 -6
- package/dist/mcp/known-archetypes.js +24 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/ATTRIBUTION.md +72 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-crate.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-ground.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-hero.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-orc.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-rock.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-tree.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-villager.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/animation.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/animation.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/art.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/art.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/bundle.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/bundle.js +9 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/controls.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/controls.js +14 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/index.d.ts +24 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/index.js +15 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/manifest.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/manifest.js +29 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/prefabs.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/prefabs.js +196 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry-data.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry-data.js +46 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry.d.ts +4 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry.js +17 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/scene-default.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/scene-default.js +49 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/ui.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/ui.js +17 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/package.json +30 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/package.json +3 -3
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/ATTRIBUTION.md +28 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-blob.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-fence.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-grass.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-hero.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-npc.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-path.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-potion.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-tree.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-water.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/animation.d.ts +7 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/animation.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/art.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/art.js +19 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/bundle.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/bundle.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/controls.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/controls.js +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/index.d.ts +25 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/index.js +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/manifest.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/manifest.js +29 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/prefabs.d.ts +11 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/prefabs.js +178 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry-data.d.ts +9 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry-data.js +55 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry.d.ts +6 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry.js +21 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/scene-default.d.ts +57 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/scene-default.js +110 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/ui.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/ui.js +26 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/package.json +30 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-motor.d.ts +56 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-motor.js +74 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-player-role.d.ts +15 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-player-role.js +71 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.js +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/interactable.d.ts +34 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/interactable.js +72 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-motor.d.ts +14 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-motor.js +36 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-player-states.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-player-states.js +9 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.d.ts +4 -2
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.js +5 -1
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-motor.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-motor.js +31 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-player-states.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-player-states.js +14 -0
- package/dist/mcp/node_modules/@waica/behaviors/package.json +2 -2
- package/dist/mcp/node_modules/@waica/engine/dist/animation/directional.d.ts +48 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/directional.js +68 -0
- package/dist/mcp/node_modules/@waica/engine/dist/archetype.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.d.ts +19 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.js +10 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.d.ts +27 -2
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.js +50 -5
- package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.js +2 -4
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.d.ts +23 -1
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.js +49 -8
- package/dist/mcp/node_modules/@waica/engine/dist/components/tilemap.d.ts +113 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/tilemap.js +316 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.js +13 -1
- package/dist/mcp/node_modules/@waica/engine/dist/game.d.ts +13 -1
- package/dist/mcp/node_modules/@waica/engine/dist/game.js +70 -9
- package/dist/mcp/node_modules/@waica/engine/dist/index.d.ts +16 -3
- package/dist/mcp/node_modules/@waica/engine/dist/index.js +8 -1
- package/dist/mcp/node_modules/@waica/engine/dist/projection.d.ts +14 -0
- package/dist/mcp/node_modules/@waica/engine/dist/projection.js +30 -0
- package/dist/mcp/node_modules/@waica/engine/dist/render-sort.d.ts +31 -0
- package/dist/mcp/node_modules/@waica/engine/dist/render-sort.js +39 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene-solids.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene-solids.js +28 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.d.ts +12 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.js +1 -0
- package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.js +2 -4
- package/dist/mcp/node_modules/@waica/engine/dist/sprite-placement.d.ts +22 -0
- package/dist/mcp/node_modules/@waica/engine/dist/sprite-placement.js +14 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.d.ts +1 -1
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.js +3 -1
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.js +57 -1
- package/dist/mcp/node_modules/@waica/engine/dist/tilemap-grid.d.ts +26 -0
- package/dist/mcp/node_modules/@waica/engine/dist/tilemap-grid.js +41 -0
- package/dist/mcp/node_modules/@waica/engine/package.json +1 -1
- package/dist/mcp/package-resolver.js +14 -4
- package/dist/mcp/project-component-loader.js +2 -1
- package/dist/mcp/project-component-runner.js +5 -0
- package/dist/mcp/server.js +18 -1
- package/dist/mcp/template/package.json.tpl +1 -1
- package/dist/mcp/template/src/main.ts +8 -4
- package/dist/mcp/workspace-runtime.js +41 -10
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@waica/archetype-topdown",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Waica top-down archetype — opinionated setup for Zelda-like overhead adventures",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/chichex/waica",
|
|
10
|
+
"directory": "packages/archetype-topdown"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"assets"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./manifest": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@waica/behaviors": "^0.8.0",
|
|
28
|
+
"@waica/engine": "^0.8.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Component, type AnimationFacingProvider, type CameraVelocity, type CameraVelocityProvider } from '@waica/engine';
|
|
2
|
+
/** Shared velocity, collision and provider behavior for square-grid motors. */
|
|
3
|
+
export declare abstract class GridMotor<Facing extends string> extends Component implements CameraVelocityProvider, AnimationFacingProvider {
|
|
4
|
+
static displayName: string;
|
|
5
|
+
static params: {
|
|
6
|
+
moveSpeed: {
|
|
7
|
+
label: string;
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
step: number;
|
|
11
|
+
};
|
|
12
|
+
acceleration: {
|
|
13
|
+
label: string;
|
|
14
|
+
min: number;
|
|
15
|
+
max: number;
|
|
16
|
+
step: number;
|
|
17
|
+
};
|
|
18
|
+
deceleration: {
|
|
19
|
+
label: string;
|
|
20
|
+
min: number;
|
|
21
|
+
max: number;
|
|
22
|
+
step: number;
|
|
23
|
+
};
|
|
24
|
+
walkThreshold: {
|
|
25
|
+
label: string;
|
|
26
|
+
min: number;
|
|
27
|
+
max: number;
|
|
28
|
+
step: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
static transient: string[];
|
|
32
|
+
moveSpeed: number;
|
|
33
|
+
acceleration: number;
|
|
34
|
+
deceleration: number;
|
|
35
|
+
/** Speed above this reads as walking (the idle ↔ walk edge). */
|
|
36
|
+
walkThreshold: number;
|
|
37
|
+
/** The character's AABB hitbox — a footprint, shorter than the sprite. */
|
|
38
|
+
hitboxWidth: number;
|
|
39
|
+
hitboxHeight: number;
|
|
40
|
+
vx: number;
|
|
41
|
+
vy: number;
|
|
42
|
+
abstract facing: Facing;
|
|
43
|
+
/** The scene camera reads follow velocity through this explicit seam. */
|
|
44
|
+
getCameraVelocity(): CameraVelocity;
|
|
45
|
+
/** The StateMachine resolves directional clips through this explicit seam. */
|
|
46
|
+
getAnimationFacing(): string;
|
|
47
|
+
/** Accelerates each axis toward an already-normalized logical direction. */
|
|
48
|
+
protected accelerateToward(x: number, y: number, dt: number): void;
|
|
49
|
+
/** Live speed, for the idle ↔ walk edge. */
|
|
50
|
+
speed(): number;
|
|
51
|
+
/** Integrates velocity and resolves each logical axis against Solids. */
|
|
52
|
+
step(dt: number): void;
|
|
53
|
+
halt(): void;
|
|
54
|
+
private resolveAxis;
|
|
55
|
+
private collisionBody;
|
|
56
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Component, resolveSolidAxis, THREE, } from '@waica/engine';
|
|
2
|
+
/** Shared velocity, collision and provider behavior for square-grid motors. */
|
|
3
|
+
export class GridMotor extends Component {
|
|
4
|
+
static displayName = 'Motor';
|
|
5
|
+
static params = {
|
|
6
|
+
moveSpeed: { label: 'Speed', min: 1, max: 30, step: 0.5 },
|
|
7
|
+
acceleration: { label: 'Acceleration', min: 5, max: 200, step: 5 },
|
|
8
|
+
deceleration: { label: 'Deceleration', min: 5, max: 200, step: 5 },
|
|
9
|
+
walkThreshold: { label: 'Walk threshold', min: 0, max: 5, step: 0.1 },
|
|
10
|
+
};
|
|
11
|
+
static transient = ['vx', 'vy', 'facing'];
|
|
12
|
+
moveSpeed = 6;
|
|
13
|
+
acceleration = 60;
|
|
14
|
+
deceleration = 80;
|
|
15
|
+
/** Speed above this reads as walking (the idle ↔ walk edge). */
|
|
16
|
+
walkThreshold = 0.5;
|
|
17
|
+
/** The character's AABB hitbox — a footprint, shorter than the sprite. */
|
|
18
|
+
hitboxWidth = 0.9;
|
|
19
|
+
hitboxHeight = 0.6;
|
|
20
|
+
vx = 0;
|
|
21
|
+
vy = 0;
|
|
22
|
+
/** The scene camera reads follow velocity through this explicit seam. */
|
|
23
|
+
getCameraVelocity() {
|
|
24
|
+
return { vx: this.vx, vy: this.vy };
|
|
25
|
+
}
|
|
26
|
+
/** The StateMachine resolves directional clips through this explicit seam. */
|
|
27
|
+
getAnimationFacing() {
|
|
28
|
+
return this.facing;
|
|
29
|
+
}
|
|
30
|
+
/** Accelerates each axis toward an already-normalized logical direction. */
|
|
31
|
+
accelerateToward(x, y, dt) {
|
|
32
|
+
const rateX = x !== 0 ? this.acceleration : this.deceleration;
|
|
33
|
+
const rateY = y !== 0 ? this.acceleration : this.deceleration;
|
|
34
|
+
this.vx = THREE.MathUtils.damp(this.vx, x * this.moveSpeed, rateX / this.moveSpeed, dt);
|
|
35
|
+
this.vy = THREE.MathUtils.damp(this.vy, y * this.moveSpeed, rateY / this.moveSpeed, dt);
|
|
36
|
+
}
|
|
37
|
+
/** Live speed, for the idle ↔ walk edge. */
|
|
38
|
+
speed() {
|
|
39
|
+
return Math.hypot(this.vx, this.vy);
|
|
40
|
+
}
|
|
41
|
+
/** Integrates velocity and resolves each logical axis against Solids. */
|
|
42
|
+
step(dt) {
|
|
43
|
+
const position = this.entity.position;
|
|
44
|
+
const previousX = position.x;
|
|
45
|
+
position.x += this.vx * dt;
|
|
46
|
+
if (this.resolveAxis('x', previousX))
|
|
47
|
+
this.vx = 0;
|
|
48
|
+
const previousY = position.y;
|
|
49
|
+
position.y += this.vy * dt;
|
|
50
|
+
if (this.resolveAxis('y', previousY))
|
|
51
|
+
this.vy = 0;
|
|
52
|
+
}
|
|
53
|
+
halt() {
|
|
54
|
+
this.vx = 0;
|
|
55
|
+
this.vy = 0;
|
|
56
|
+
}
|
|
57
|
+
resolveAxis(axis, previous) {
|
|
58
|
+
return resolveSolidAxis({
|
|
59
|
+
entity: this.entity,
|
|
60
|
+
axis,
|
|
61
|
+
previous,
|
|
62
|
+
body: () => this.collisionBody(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
collisionBody() {
|
|
66
|
+
return {
|
|
67
|
+
x: this.entity.position.x,
|
|
68
|
+
y: this.entity.position.y,
|
|
69
|
+
width: this.hitboxWidth,
|
|
70
|
+
height: this.hitboxHeight,
|
|
71
|
+
shape: 'rectangle',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Component, type ComponentClass, type RoleDefinition, type RoleGraph, type StateContext } from '@waica/engine';
|
|
2
|
+
interface PlayerMotor extends Component {
|
|
3
|
+
walkThreshold: number;
|
|
4
|
+
run(inputX: number, inputY: number, dt: number): void;
|
|
5
|
+
step(dt: number): void;
|
|
6
|
+
speed(): number;
|
|
7
|
+
}
|
|
8
|
+
interface GridPlayerRoleParts {
|
|
9
|
+
graph: RoleGraph;
|
|
10
|
+
update(ctx: StateContext, dt: number): void;
|
|
11
|
+
role: RoleDefinition;
|
|
12
|
+
}
|
|
13
|
+
/** Builds the common player graph and role around one genre-specific grid motor. */
|
|
14
|
+
export declare function createGridPlayerRole<T extends PlayerMotor>(Motor: ComponentClass<T>, description: string): GridPlayerRoleParts;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {} from '@waica/engine';
|
|
2
|
+
import { Health } from './health.js';
|
|
3
|
+
import { interactUpdate } from './interactable.js';
|
|
4
|
+
import { Respawnable } from './respawnable.js';
|
|
5
|
+
/** Builds the common player graph and role around one genre-specific grid motor. */
|
|
6
|
+
export function createGridPlayerRole(Motor, description) {
|
|
7
|
+
const graph = {
|
|
8
|
+
initial: 'idle',
|
|
9
|
+
states: {
|
|
10
|
+
idle: {
|
|
11
|
+
transitions: [{ on: 'signal:move', to: 'walk' }],
|
|
12
|
+
},
|
|
13
|
+
walk: {
|
|
14
|
+
transitions: [{ on: 'signal:stop', to: 'idle' }],
|
|
15
|
+
},
|
|
16
|
+
dead: {
|
|
17
|
+
// Freeze on idle (resolved directionally) rather than name a death
|
|
18
|
+
// clip the stock sheets do not have and warn every frame.
|
|
19
|
+
clip: 'idle',
|
|
20
|
+
transitions: [{ on: 'timer:0.8', to: 'idle' }],
|
|
21
|
+
},
|
|
22
|
+
// Dying is the same from every state, so the edge lives on '*'. Its
|
|
23
|
+
// presence is also the contract Health checks before signalling: a graph
|
|
24
|
+
// without it gets its entity destroyed instead.
|
|
25
|
+
'*': { transitions: [{ on: 'signal:death', to: 'dead' }] },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const update = ({ entity, game, fsm }, dt) => {
|
|
29
|
+
const motor = entity.get(Motor);
|
|
30
|
+
if (!motor)
|
|
31
|
+
return;
|
|
32
|
+
motor.run(game.input.axis('left', 'right'), game.input.axis('down', 'up'), dt);
|
|
33
|
+
motor.step(dt);
|
|
34
|
+
fsm.signal(motor.speed() > motor.walkThreshold ? 'move' : 'stop');
|
|
35
|
+
};
|
|
36
|
+
const role = {
|
|
37
|
+
description,
|
|
38
|
+
driver: Motor.componentName,
|
|
39
|
+
graph,
|
|
40
|
+
signals: {
|
|
41
|
+
move: 'moving',
|
|
42
|
+
stop: 'standing still',
|
|
43
|
+
},
|
|
44
|
+
states: {
|
|
45
|
+
// Always-hook: the interact lookup must survive custom states like an
|
|
46
|
+
// attack, so it runs in every state.
|
|
47
|
+
'*': {
|
|
48
|
+
onUpdate(ctx) {
|
|
49
|
+
interactUpdate(ctx);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
// Fallback: a custom state without its own onUpdate keeps the full body
|
|
53
|
+
// update — its file only has to say what makes it special.
|
|
54
|
+
default: { onUpdate: update },
|
|
55
|
+
idle: { onUpdate: update },
|
|
56
|
+
walk: { onUpdate: update },
|
|
57
|
+
// Coming back is what leaving death means, so it hangs off onExit: any
|
|
58
|
+
// other way out of this state (a project's own edge) revives too.
|
|
59
|
+
dead: {
|
|
60
|
+
// A state without its own onUpdate falls back to the role's default
|
|
61
|
+
// body update, so this no-op keeps the player still for the death beat.
|
|
62
|
+
onUpdate() { },
|
|
63
|
+
onExit({ entity }) {
|
|
64
|
+
entity.get(Respawnable)?.respawn();
|
|
65
|
+
entity.get(Health)?.heal(Infinity);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return { graph, update, role };
|
|
71
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { PlatformerMotor } from './platformer-motor.js';
|
|
2
2
|
export { PLAYER_ROLE, PLAYER_STATE_GRAPH, playerUpdate } from './player-states.js';
|
|
3
|
+
export { TopDownMotor, type TopDownFacing } from './topdown-motor.js';
|
|
4
|
+
export { IsoMotor, type IsoFacing } from './iso-motor.js';
|
|
5
|
+
export { TOPDOWN_PLAYER_ROLE, TOPDOWN_PLAYER_STATE_GRAPH, topdownPlayerUpdate, } from './topdown-player-states.js';
|
|
6
|
+
export { ISO_PLAYER_ROLE, ISO_PLAYER_STATE_GRAPH, isoPlayerUpdate, } from './iso-player-states.js';
|
|
7
|
+
export { INTERACTABLE_UI, INTERACTABLE_UI_PIECE, Interactable, interactUpdate, } from './interactable.js';
|
|
3
8
|
export { Collectible } from './collectible.js';
|
|
4
9
|
export { Patrol, PATROLLER_ROLE, PATROLLER_STATE_GRAPH, type PatrolAxis } from './patrol.js';
|
|
5
10
|
export { Chaser, CHASER_ROLE, CHASER_STATE_GRAPH, type ChaserMode } from './chaser.js';
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { PlatformerMotor } from './platformer-motor.js';
|
|
2
2
|
export { PLAYER_ROLE, PLAYER_STATE_GRAPH, playerUpdate } from './player-states.js';
|
|
3
|
+
export { TopDownMotor } from './topdown-motor.js';
|
|
4
|
+
export { IsoMotor } from './iso-motor.js';
|
|
5
|
+
export { TOPDOWN_PLAYER_ROLE, TOPDOWN_PLAYER_STATE_GRAPH, topdownPlayerUpdate, } from './topdown-player-states.js';
|
|
6
|
+
export { ISO_PLAYER_ROLE, ISO_PLAYER_STATE_GRAPH, isoPlayerUpdate, } from './iso-player-states.js';
|
|
7
|
+
export { INTERACTABLE_UI, INTERACTABLE_UI_PIECE, Interactable, interactUpdate, } from './interactable.js';
|
|
3
8
|
export { Collectible } from './collectible.js';
|
|
4
9
|
export { Patrol, PATROLLER_ROLE, PATROLLER_STATE_GRAPH } from './patrol.js';
|
|
5
10
|
export { Chaser, CHASER_ROLE, CHASER_STATE_GRAPH } from './chaser.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Component, type StateContext } from '@waica/engine';
|
|
2
|
+
/**
|
|
3
|
+
* Something the player can talk to or examine: a dialogue line and the
|
|
4
|
+
* radius it can be triggered from. The component is pure data — the
|
|
5
|
+
* player role's always-hook does the lookup (see interactUpdate), so an
|
|
6
|
+
* NPC stays code-free: Interactable + the npc role is a whole villager.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Interactable extends Component {
|
|
9
|
+
static componentName: string;
|
|
10
|
+
static params: {
|
|
11
|
+
line: {
|
|
12
|
+
label: string;
|
|
13
|
+
};
|
|
14
|
+
radius: {
|
|
15
|
+
label: string;
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
step: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** What pressing interact within the radius says. */
|
|
22
|
+
line: string;
|
|
23
|
+
radius: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const INTERACTABLE_UI_PIECE = "npc-line";
|
|
26
|
+
/** The UI fragment every archetype using Interactable must register. */
|
|
27
|
+
export declare const INTERACTABLE_UI: Readonly<Record<string, string>>;
|
|
28
|
+
/**
|
|
29
|
+
* The player role's interact lookup, run by its '*' hook in every state:
|
|
30
|
+
* pressing interact near an Interactable publishes its line through the
|
|
31
|
+
* npcLine stat and shows the npc-line UI piece; walking out of every
|
|
32
|
+
* radius hides it again. Nearest one wins when several are in range.
|
|
33
|
+
*/
|
|
34
|
+
export declare function interactUpdate({ entity, game }: StateContext): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Component } from '@waica/engine';
|
|
2
|
+
/**
|
|
3
|
+
* Something the player can talk to or examine: a dialogue line and the
|
|
4
|
+
* radius it can be triggered from. The component is pure data — the
|
|
5
|
+
* player role's always-hook does the lookup (see interactUpdate), so an
|
|
6
|
+
* NPC stays code-free: Interactable + the npc role is a whole villager.
|
|
7
|
+
*/
|
|
8
|
+
export class Interactable extends Component {
|
|
9
|
+
static componentName = 'Interactable';
|
|
10
|
+
static params = {
|
|
11
|
+
line: { label: 'Line' },
|
|
12
|
+
radius: { label: 'Radius', min: 0.5, max: 10, step: 0.25 },
|
|
13
|
+
};
|
|
14
|
+
/** What pressing interact within the radius says. */
|
|
15
|
+
line = 'Hello, traveler!';
|
|
16
|
+
radius = 1.5;
|
|
17
|
+
}
|
|
18
|
+
export const INTERACTABLE_UI_PIECE = 'npc-line';
|
|
19
|
+
/** The UI fragment every archetype using Interactable must register. */
|
|
20
|
+
export const INTERACTABLE_UI = {
|
|
21
|
+
[INTERACTABLE_UI_PIECE]: `<style>
|
|
22
|
+
.npc-line {
|
|
23
|
+
position: absolute;
|
|
24
|
+
left: 50%;
|
|
25
|
+
bottom: 24px;
|
|
26
|
+
transform: translateX(-50%);
|
|
27
|
+
max-width: 70%;
|
|
28
|
+
padding: 10px 18px;
|
|
29
|
+
border-radius: 8px;
|
|
30
|
+
background: #1a1a2ecc;
|
|
31
|
+
border: 1px solid #ffffff2e;
|
|
32
|
+
font: 500 18px system-ui, sans-serif;
|
|
33
|
+
color: #f5f5f5;
|
|
34
|
+
text-shadow: 0 1px 2px #000a;
|
|
35
|
+
user-select: none;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
38
|
+
<div class="npc-line">{{npcLine}}</div>
|
|
39
|
+
`,
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The player role's interact lookup, run by its '*' hook in every state:
|
|
43
|
+
* pressing interact near an Interactable publishes its line through the
|
|
44
|
+
* npcLine stat and shows the npc-line UI piece; walking out of every
|
|
45
|
+
* radius hides it again. Nearest one wins when several are in range.
|
|
46
|
+
*/
|
|
47
|
+
export function interactUpdate({ entity, game }) {
|
|
48
|
+
let nearest = null;
|
|
49
|
+
let nearestDistance = Infinity;
|
|
50
|
+
for (const other of game.entities) {
|
|
51
|
+
if (other === entity)
|
|
52
|
+
continue;
|
|
53
|
+
const interactable = other.get(Interactable);
|
|
54
|
+
if (!interactable)
|
|
55
|
+
continue;
|
|
56
|
+
const distance = Math.hypot(other.position.x - entity.position.x, other.position.y - entity.position.y);
|
|
57
|
+
if (distance <= interactable.radius && distance < nearestDistance) {
|
|
58
|
+
nearest = interactable;
|
|
59
|
+
nearestDistance = distance;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!nearest) {
|
|
63
|
+
game.ui.hide(INTERACTABLE_UI_PIECE);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (game.input.justPressed('interact') && !game.input.consumed('interact')) {
|
|
67
|
+
// The press is spent: an input:interact edge needs a NEW press.
|
|
68
|
+
game.input.consume('interact');
|
|
69
|
+
game.stats.set('npcLine', nearest.line);
|
|
70
|
+
game.ui.show(INTERACTABLE_UI_PIECE);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { GridMotor } from './grid-motor.js';
|
|
2
|
+
/** The eight screen-relative facings declared by the isometric archetype. */
|
|
3
|
+
export type IsoFacing = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';
|
|
4
|
+
/**
|
|
5
|
+
* Passive isometric motor. Input and facing are screen-relative while the
|
|
6
|
+
* shared GridMotor integrates and collides in logical square-grid space.
|
|
7
|
+
* State code owns the frame; this component has no onUpdate of its own.
|
|
8
|
+
*/
|
|
9
|
+
export declare class IsoMotor extends GridMotor<IsoFacing> {
|
|
10
|
+
static componentName: string;
|
|
11
|
+
facing: IsoFacing;
|
|
12
|
+
/** Accelerates screen-relative input toward a logical-space velocity. */
|
|
13
|
+
run(inputX: number, inputY: number, dt: number): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { screenInputToLogical } from '@waica/engine';
|
|
2
|
+
import { GridMotor } from './grid-motor.js';
|
|
3
|
+
function facingForInput(inputX, inputY) {
|
|
4
|
+
if (inputX === 0 && inputY === 0)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (inputY > 0) {
|
|
7
|
+
if (inputX > 0)
|
|
8
|
+
return 'ne';
|
|
9
|
+
if (inputX < 0)
|
|
10
|
+
return 'nw';
|
|
11
|
+
return 'n';
|
|
12
|
+
}
|
|
13
|
+
if (inputY < 0) {
|
|
14
|
+
if (inputX > 0)
|
|
15
|
+
return 'se';
|
|
16
|
+
if (inputX < 0)
|
|
17
|
+
return 'sw';
|
|
18
|
+
return 's';
|
|
19
|
+
}
|
|
20
|
+
return inputX > 0 ? 'e' : 'w';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Passive isometric motor. Input and facing are screen-relative while the
|
|
24
|
+
* shared GridMotor integrates and collides in logical square-grid space.
|
|
25
|
+
* State code owns the frame; this component has no onUpdate of its own.
|
|
26
|
+
*/
|
|
27
|
+
export class IsoMotor extends GridMotor {
|
|
28
|
+
static componentName = 'IsoMotor';
|
|
29
|
+
facing = 's';
|
|
30
|
+
/** Accelerates screen-relative input toward a logical-space velocity. */
|
|
31
|
+
run(inputX, inputY, dt) {
|
|
32
|
+
const direction = screenInputToLogical(inputX, inputY);
|
|
33
|
+
this.accelerateToward(direction.x, direction.y, dt);
|
|
34
|
+
this.facing = facingForInput(inputX, inputY) ?? this.facing;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The state graph new isometric player characters start with, as prefab data. */
|
|
2
|
+
export declare const ISO_PLAYER_STATE_GRAPH: import("@waica/engine").RoleGraph;
|
|
3
|
+
/** Drives IsoMotor from screen-relative actions and reports body state. */
|
|
4
|
+
export declare const isoPlayerUpdate: (ctx: import("@waica/engine").StateContext, dt: number) => void;
|
|
5
|
+
export declare const ISO_PLAYER_ROLE: import("@waica/engine").RoleDefinition;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createGridPlayerRole } from './grid-player-role.js';
|
|
2
|
+
import { IsoMotor } from './iso-motor.js';
|
|
3
|
+
const isoPlayer = createGridPlayerRole(IsoMotor, 'You control this character with screen-relative eight-direction movement ' +
|
|
4
|
+
'projected into an isometric logical world. Its states move the Motor.');
|
|
5
|
+
/** The state graph new isometric player characters start with, as prefab data. */
|
|
6
|
+
export const ISO_PLAYER_STATE_GRAPH = isoPlayer.graph;
|
|
7
|
+
/** Drives IsoMotor from screen-relative actions and reports body state. */
|
|
8
|
+
export const isoPlayerUpdate = isoPlayer.update;
|
|
9
|
+
export const ISO_PLAYER_ROLE = isoPlayer.role;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component } from '@waica/engine';
|
|
1
|
+
import { Component, type CameraVelocity, type CameraVelocityProvider } from '@waica/engine';
|
|
2
2
|
/**
|
|
3
3
|
* Passive platformer motor: tuning params, physical state and movement
|
|
4
4
|
* methods for state code to call. It has no onUpdate of its own — the
|
|
@@ -9,7 +9,7 @@ import { Component } from '@waica/engine';
|
|
|
9
9
|
* Per-axis collision against the scene's Solids — deterministic genre
|
|
10
10
|
* movement (Celeste-style), not "realistic" physics.
|
|
11
11
|
*/
|
|
12
|
-
export declare class PlatformerMotor extends Component {
|
|
12
|
+
export declare class PlatformerMotor extends Component implements CameraVelocityProvider {
|
|
13
13
|
static componentName: string;
|
|
14
14
|
static displayName: string;
|
|
15
15
|
static params: {
|
|
@@ -103,6 +103,8 @@ export declare class PlatformerMotor extends Component {
|
|
|
103
103
|
private bufferTimer;
|
|
104
104
|
private squashX;
|
|
105
105
|
private squashY;
|
|
106
|
+
/** The scene camera reads follow velocity through this explicit seam. */
|
|
107
|
+
getCameraVelocity(): CameraVelocity;
|
|
106
108
|
/**
|
|
107
109
|
* Per-frame bookkeeping: forgiveness timers, squash decay and the
|
|
108
110
|
* facing flip. The logic set's '*' hook runs it in every state, so
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, resolveSolidAxis, THREE } from '@waica/engine';
|
|
1
|
+
import { Component, resolveSolidAxis, THREE, } from '@waica/engine';
|
|
2
2
|
/**
|
|
3
3
|
* Passive platformer motor: tuning params, physical state and movement
|
|
4
4
|
* methods for state code to call. It has no onUpdate of its own — the
|
|
@@ -60,6 +60,10 @@ export class PlatformerMotor extends Component {
|
|
|
60
60
|
bufferTimer = 0;
|
|
61
61
|
squashX = 1;
|
|
62
62
|
squashY = 1;
|
|
63
|
+
/** The scene camera reads follow velocity through this explicit seam. */
|
|
64
|
+
getCameraVelocity() {
|
|
65
|
+
return { vx: this.vx, vy: this.vy };
|
|
66
|
+
}
|
|
63
67
|
/**
|
|
64
68
|
* Per-frame bookkeeping: forgiveness timers, squash decay and the
|
|
65
69
|
* facing flip. The logic set's '*' hook runs it in every state, so
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { GridMotor } from './grid-motor.js';
|
|
2
|
+
/** The four facings the top-down animation contract declares. */
|
|
3
|
+
export type TopDownFacing = 'n' | 's' | 'e' | 'w';
|
|
4
|
+
/**
|
|
5
|
+
* Passive top-down motor: state code owns the frame and calls run() then
|
|
6
|
+
* step(). Eight-direction input is normalized so diagonals match cardinal
|
|
7
|
+
* speed; movement has no gravity and resolves each axis against Solids.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TopDownMotor extends GridMotor<TopDownFacing> {
|
|
10
|
+
static componentName: string;
|
|
11
|
+
/** Four-direction facing; starts looking at the camera. */
|
|
12
|
+
facing: TopDownFacing;
|
|
13
|
+
/**
|
|
14
|
+
* Accelerates toward the input vector and lets the dominant raw input axis
|
|
15
|
+
* select facing. Perfect ties preserve the previous facing.
|
|
16
|
+
*/
|
|
17
|
+
run(inputX: number, inputY: number, dt: number): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { GridMotor } from './grid-motor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Passive top-down motor: state code owns the frame and calls run() then
|
|
4
|
+
* step(). Eight-direction input is normalized so diagonals match cardinal
|
|
5
|
+
* speed; movement has no gravity and resolves each axis against Solids.
|
|
6
|
+
*/
|
|
7
|
+
export class TopDownMotor extends GridMotor {
|
|
8
|
+
static componentName = 'TopDownMotor';
|
|
9
|
+
/** Four-direction facing; starts looking at the camera. */
|
|
10
|
+
facing = 's';
|
|
11
|
+
/**
|
|
12
|
+
* Accelerates toward the input vector and lets the dominant raw input axis
|
|
13
|
+
* select facing. Perfect ties preserve the previous facing.
|
|
14
|
+
*/
|
|
15
|
+
run(inputX, inputY, dt) {
|
|
16
|
+
let x = inputX;
|
|
17
|
+
let y = inputY;
|
|
18
|
+
const length = Math.hypot(x, y);
|
|
19
|
+
if (length > 1) {
|
|
20
|
+
x /= length;
|
|
21
|
+
y /= length;
|
|
22
|
+
}
|
|
23
|
+
this.accelerateToward(x, y, dt);
|
|
24
|
+
const absX = Math.abs(inputX);
|
|
25
|
+
const absY = Math.abs(inputY);
|
|
26
|
+
if (absX > absY)
|
|
27
|
+
this.facing = inputX < 0 ? 'w' : 'e';
|
|
28
|
+
else if (absY > absX)
|
|
29
|
+
this.facing = inputY < 0 ? 's' : 'n';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The state graph new top-down player characters start with, as prefab data. */
|
|
2
|
+
export declare const TOPDOWN_PLAYER_STATE_GRAPH: import("@waica/engine").RoleGraph;
|
|
3
|
+
/** Drives TopDownMotor from project actions and reports body state. */
|
|
4
|
+
export declare const topdownPlayerUpdate: (ctx: import("@waica/engine").StateContext, dt: number) => void;
|
|
5
|
+
export declare const TOPDOWN_PLAYER_ROLE: import("@waica/engine").RoleDefinition;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createGridPlayerRole } from './grid-player-role.js';
|
|
2
|
+
import { TopDownMotor } from './topdown-motor.js';
|
|
3
|
+
/**
|
|
4
|
+
* The top-down player role: eight-direction logical-grid movement with
|
|
5
|
+
* normalized diagonals and no gravity, driven by TopDownMotor.
|
|
6
|
+
*/
|
|
7
|
+
const topdownPlayer = createGridPlayerRole(TopDownMotor, 'You control this character with the project controls: eight-direction ' +
|
|
8
|
+
'top-down movement with normalized diagonals and no gravity. Its ' +
|
|
9
|
+
'states move the Motor.');
|
|
10
|
+
/** The state graph new top-down player characters start with, as prefab data. */
|
|
11
|
+
export const TOPDOWN_PLAYER_STATE_GRAPH = topdownPlayer.graph;
|
|
12
|
+
/** Drives TopDownMotor from project actions and reports body state. */
|
|
13
|
+
export const topdownPlayerUpdate = topdownPlayer.update;
|
|
14
|
+
export const TOPDOWN_PLAYER_ROLE = topdownPlayer.role;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waica/behaviors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Waica built-in behaviors — the curated game-feel library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@waica/engine": "^0.
|
|
22
|
+
"@waica/engine": "^0.8.0"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AnimationContract } from './contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Directional animation contract: the archetype declares which facing
|
|
4
|
+
* directions exist, how clips are named per direction (`<state>-<dir>`),
|
|
5
|
+
* and how a missing direction degrades — including mirroring, so west can
|
|
6
|
+
* reuse east art flipped. Extends the base AnimationContract thesis to
|
|
7
|
+
* genres where characters face more than one way (top-down, isometric).
|
|
8
|
+
*/
|
|
9
|
+
export interface DirectionalFallback<Dir extends string = string> {
|
|
10
|
+
dir: Dir;
|
|
11
|
+
/** Mirror the resolved clip horizontally (e.g. west plays east flipped). */
|
|
12
|
+
flip?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface DirectionalAnimation<Dir extends string = string> {
|
|
15
|
+
/** Declared facing directions, e.g. ['n', 's', 'e', 'w'] — 8 for isometric. */
|
|
16
|
+
directions: readonly Dir[];
|
|
17
|
+
/** Directional degradation: a missing facing resolves to another, optionally mirrored. */
|
|
18
|
+
fallbacks?: Partial<Record<Dir, DirectionalFallback<Dir>>>;
|
|
19
|
+
/** State-level degradation chain applied when no directional clip resolves. */
|
|
20
|
+
contract: AnimationContract;
|
|
21
|
+
}
|
|
22
|
+
export interface ResolvedDirectionalClip {
|
|
23
|
+
clip: string | undefined;
|
|
24
|
+
flip: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves state × facing to a playable clip: the exact `<state>-<dir>`
|
|
28
|
+
* clip, else the declared directional fallback chain (accumulating flips).
|
|
29
|
+
* On a dead end, walks the base AnimationContract's state fallback chain
|
|
30
|
+
* (cycle-safe) and, for each candidate state in turn, retries the
|
|
31
|
+
* directional chain against it before trying its bare name. An invalid
|
|
32
|
+
* facing, or a chain that dead-ends everywhere, resolves to nothing rather
|
|
33
|
+
* than guessing — the caller's name-based fallback path handles that.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveDirectionalClip(animation: DirectionalAnimation, available: Iterable<string>, state: string, facing: string): ResolvedDirectionalClip;
|
|
36
|
+
/** Installs (or clears, with null) the active directional contract. */
|
|
37
|
+
export declare function installDirectionalAnimation(animation: DirectionalAnimation | null): void;
|
|
38
|
+
/** The active directional contract, if an archetype installed one. */
|
|
39
|
+
export declare function installedDirectionalAnimation(): DirectionalAnimation | null;
|
|
40
|
+
/**
|
|
41
|
+
* The explicit seam a driving component (a motor) opts into so StateMachine
|
|
42
|
+
* can resolve directional clips: it reports the entity's current facing as
|
|
43
|
+
* one of the contract's declared directions.
|
|
44
|
+
*/
|
|
45
|
+
export interface AnimationFacingProvider {
|
|
46
|
+
getAnimationFacing(): string;
|
|
47
|
+
}
|
|
48
|
+
export declare function isAnimationFacingProvider(value: unknown): value is AnimationFacingProvider;
|