@ludra/core 0.5.0 → 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/dist/ascii.js +2 -2
- package/dist/assertions.d.ts +5 -1
- package/dist/assertions.js +8 -4
- package/dist/browser.d.ts +8 -1
- package/dist/browser.js +282 -30
- package/dist/components.d.ts +8 -1
- package/dist/components.js +33 -1
- package/dist/drawcheck.d.ts +7 -0
- package/dist/drawcheck.js +81 -0
- package/dist/effects.d.ts +3 -2
- package/dist/effects.js +20 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/postfx.d.ts +18 -0
- package/dist/postfx.js +100 -0
- package/dist/script.d.ts +82 -3
- package/dist/script.js +25 -4
- package/dist/types.d.ts +29 -1
- package/dist/types.js +1 -1
- package/dist/validate.d.ts +3 -1
- package/dist/validate.js +120 -7
- package/dist/world.d.ts +67 -4
- package/dist/world.js +249 -35
- package/package.json +1 -1
- package/src/ascii.ts +2 -2
- package/src/assertions.ts +12 -4
- package/src/browser.ts +286 -30
- package/src/components.ts +43 -1
- package/src/drawcheck.ts +80 -0
- package/src/effects.ts +18 -2
- package/src/index.ts +1 -0
- package/src/postfx.ts +110 -0
- package/src/script.ts +101 -5
- package/src/types.ts +31 -1
- package/src/validate.ts +126 -7
- package/src/world.ts +247 -39
package/dist/validate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COMPONENT_NAMES, COMPONENTS, isComponentName, isFrameList, PARTICLE_FIELDS } from "./components.js";
|
|
2
|
-
import { AUDIO_EXTENSIONS, EFFECT_FIELDS, IMAGE_EXTENSIONS, isSoundPreset, SOUND_PRESETS } from "./effects.js";
|
|
2
|
+
import { AUDIO_EXTENSIONS, EFFECT_FIELDS, FONT_EXTENSIONS, IMAGE_EXTENSIONS, isSoundPreset, SOUND_PRESETS } from "./effects.js";
|
|
3
3
|
import { EVENT_TYPES } from "./types.js";
|
|
4
4
|
const REQUIRED_EXAMPLES = {
|
|
5
5
|
transform: '{ "x": 0, "y": 0 }',
|
|
@@ -9,18 +9,22 @@ const REQUIRED_EXAMPLES = {
|
|
|
9
9
|
* Checks a parsed scene file. Every diagnostic names the exact JSON path and,
|
|
10
10
|
* where possible, a fix an agent can apply without guessing.
|
|
11
11
|
*/
|
|
12
|
-
export function validateScene(
|
|
12
|
+
export function validateScene(input) {
|
|
13
13
|
const diagnostics = [];
|
|
14
14
|
const error = (code, path, message, fix) => diagnostics.push({ severity: "error", code, path, message, fix });
|
|
15
15
|
const warn = (code, path, message, fix) => diagnostics.push({ severity: "warning", code, path, message, fix });
|
|
16
16
|
const result = () => {
|
|
17
17
|
const ok = !diagnostics.some((d) => d.severity === "error");
|
|
18
|
-
return { ok, diagnostics, scene: ok ?
|
|
18
|
+
return { ok, diagnostics, scene: ok ? scene : undefined };
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
let scene = input;
|
|
21
|
+
if (!isObject(input)) {
|
|
21
22
|
error("E_SCENE_NOT_OBJECT", "$", "A scene must be a JSON object.", 'Start from { "entities": [] }.');
|
|
22
23
|
return result();
|
|
23
24
|
}
|
|
25
|
+
// Prefabs become plain entities first, so everything below sees the final components.
|
|
26
|
+
const raw = expandPrefabs(input, error);
|
|
27
|
+
scene = raw;
|
|
24
28
|
if (raw.world !== undefined) {
|
|
25
29
|
if (!isObject(raw.world)) {
|
|
26
30
|
error("E_WORLD_NOT_OBJECT", "world", '"world" must be an object.', 'Use "world": { "width": 960, "height": 540 }.');
|
|
@@ -38,10 +42,18 @@ export function validateScene(raw) {
|
|
|
38
42
|
if (raw.world.background !== undefined && typeof raw.world.background !== "string") {
|
|
39
43
|
error("E_INVALID_WORLD", "world.background", `world.background must be a CSS color string, got ${describe(raw.world.background)}.`);
|
|
40
44
|
}
|
|
45
|
+
const lives = raw.world.lives;
|
|
46
|
+
if (lives !== undefined && (typeof lives !== "number" || !Number.isInteger(lives) || lives < 1)) {
|
|
47
|
+
error("E_INVALID_WORLD", "world.lives", `world.lives must be a whole number of at least 1, got ${describe(lives)}.`, "Leave it out for unlimited retries.");
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
if (raw.effects !== undefined)
|
|
44
52
|
validateEffects(raw.effects, error, warn);
|
|
53
|
+
if (raw.fonts !== undefined)
|
|
54
|
+
validateFonts(raw.fonts, error);
|
|
55
|
+
if (raw.postfx !== undefined)
|
|
56
|
+
validatePostFx(raw.postfx, error);
|
|
45
57
|
if (!Array.isArray(raw.entities)) {
|
|
46
58
|
error("E_NO_ENTITIES", "entities", 'A scene needs an "entities" array.', 'Add "entities": [].');
|
|
47
59
|
return result();
|
|
@@ -49,6 +61,7 @@ export function validateScene(raw) {
|
|
|
49
61
|
const ids = new Map();
|
|
50
62
|
let hasPlayer = false;
|
|
51
63
|
let hasDynamic = false;
|
|
64
|
+
const worldGravity = isObject(raw.world) && typeof raw.world.gravity === "number" ? raw.world.gravity : 1800;
|
|
52
65
|
let hasSolid = false;
|
|
53
66
|
let availableScore = 0;
|
|
54
67
|
const goals = [];
|
|
@@ -106,6 +119,12 @@ export function validateScene(raw) {
|
|
|
106
119
|
}
|
|
107
120
|
const body = isObject(components.body) ? components.body : undefined;
|
|
108
121
|
const bodyType = body?.type ?? "static";
|
|
122
|
+
if ("topdown" in components && body && bodyType === "static") {
|
|
123
|
+
error("E_TOPDOWN_NEEDS_MOVING_BODY", `${path}.components.body.type`, `Entity ${label} has "topdown" but a static body, so it can never move.`, 'Use "type": "dynamic" to collide with walls, or "kinematic" to pass through them (a paddle or a ship).');
|
|
124
|
+
}
|
|
125
|
+
if ("topdown" in components && "platformer" in components) {
|
|
126
|
+
error("E_TWO_CONTROLLERS", `${path}.components`, `Entity ${label} has both "platformer" and "topdown", which would fight over its movement.`, "Keep one: platformer for side-view games with gravity, topdown for top-down games, paddles and ships.");
|
|
127
|
+
}
|
|
109
128
|
if ("platformer" in components && body && bodyType !== "dynamic") {
|
|
110
129
|
error("E_PLATFORMER_NEEDS_DYNAMIC_BODY", `${path}.components.body.type`, `Entity ${label} has "platformer" but its body is "${String(bodyType)}", so it can never move.`, 'Set "type": "dynamic" on its body.');
|
|
111
130
|
}
|
|
@@ -123,9 +142,11 @@ export function validateScene(raw) {
|
|
|
123
142
|
availableScore += numberOr(components.hazard.score, 0);
|
|
124
143
|
if (isObject(components.goal))
|
|
125
144
|
goals.push({ path: `${path}.components.goal.requireScore`, label, requireScore: numberOr(components.goal.requireScore, 0) });
|
|
126
|
-
if ("platformer" in components)
|
|
145
|
+
if ("platformer" in components || "topdown" in components)
|
|
127
146
|
hasPlayer = true;
|
|
128
|
-
|
|
147
|
+
// Only bodies that gravity actually pulls down need something to stand on.
|
|
148
|
+
const falls = body && bodyType === "dynamic" && !("topdown" in components) && body.gravityScale !== 0;
|
|
149
|
+
if (falls && worldGravity !== 0)
|
|
129
150
|
hasDynamic = true;
|
|
130
151
|
if (body && bodyType !== "dynamic" && body.sensor !== true)
|
|
131
152
|
hasSolid = true;
|
|
@@ -139,7 +160,7 @@ export function validateScene(raw) {
|
|
|
139
160
|
}
|
|
140
161
|
}
|
|
141
162
|
if (!hasPlayer) {
|
|
142
|
-
warn("W_NO_PLAYER", "entities", 'No entity has a "platformer" component, so nothing responds to input.');
|
|
163
|
+
warn("W_NO_PLAYER", "entities", 'No entity has a "platformer" or "topdown" component, so nothing responds to input (fine if scripts read input).');
|
|
143
164
|
}
|
|
144
165
|
return result();
|
|
145
166
|
}
|
|
@@ -148,7 +169,68 @@ const SENSOR_COMPONENTS = [
|
|
|
148
169
|
["collectible", "players would bump into it instead of picking it up"],
|
|
149
170
|
["hazard", "players would stand on it instead of being hurt"],
|
|
150
171
|
["goal", "players would bump into it instead of reaching it"],
|
|
172
|
+
["checkpoint", "players would bump into it instead of passing through"],
|
|
151
173
|
];
|
|
174
|
+
/**
|
|
175
|
+
* Replaces `"prefab": "<name>"` entities with the prefab's components, with the
|
|
176
|
+
* entity's own components merged over them (field by field; `null` removes a
|
|
177
|
+
* component). Returns a new scene object; reports unknown prefabs.
|
|
178
|
+
*/
|
|
179
|
+
function expandPrefabs(raw, error) {
|
|
180
|
+
const prefabs = raw.prefabs;
|
|
181
|
+
if (prefabs !== undefined) {
|
|
182
|
+
if (!isObject(prefabs)) {
|
|
183
|
+
error("E_PREFABS_NOT_OBJECT", "prefabs", '"prefabs" must map names to { "components": { ... } }.');
|
|
184
|
+
return raw;
|
|
185
|
+
}
|
|
186
|
+
for (const [name, prefab] of Object.entries(prefabs)) {
|
|
187
|
+
if (!isObject(prefab) || !isObject(prefab.components)) {
|
|
188
|
+
error("E_INVALID_PREFAB", `prefabs.${name}`, `Prefab "${name}" needs a "components" object.`, 'Use { "components": { "body": { ... }, "sprite": { ... } } }.');
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
for (const component of Object.keys(prefab.components)) {
|
|
192
|
+
if (!isComponentName(component)) {
|
|
193
|
+
const suggestion = closestName(component, COMPONENT_NAMES);
|
|
194
|
+
error("E_UNKNOWN_COMPONENT", `prefabs.${name}.components.${component}`, `Unknown component "${component}" in prefab "${name}".`, suggestion ? `Did you mean "${suggestion}"?` : undefined);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!Array.isArray(raw.entities))
|
|
200
|
+
return raw;
|
|
201
|
+
const known = isObject(prefabs) ? prefabs : {};
|
|
202
|
+
const entities = raw.entities.map((entity, i) => {
|
|
203
|
+
if (!isObject(entity) || entity.prefab === undefined)
|
|
204
|
+
return entity;
|
|
205
|
+
const name = entity.prefab;
|
|
206
|
+
const prefab = typeof name === "string" ? known[name] : undefined;
|
|
207
|
+
if (!isObject(prefab) || !isObject(prefab.components)) {
|
|
208
|
+
const names = Object.keys(known);
|
|
209
|
+
const suggestion = typeof name === "string" ? closestName(name, names) : undefined;
|
|
210
|
+
error("E_UNKNOWN_PREFAB", `entities[${i}].prefab`, `Prefab ${describe(name)} does not exist.`, suggestion ? `Did you mean "${suggestion}"?` : names.length > 0 ? `Prefabs: ${names.join(", ")}.` : "Create prefabs/<name>.prefab.json with { \"components\": { ... } }.");
|
|
211
|
+
return entity;
|
|
212
|
+
}
|
|
213
|
+
const { prefab: _, ...rest } = entity;
|
|
214
|
+
return { ...rest, components: mergeComponents(prefab.components, isObject(entity.components) ? entity.components : {}) };
|
|
215
|
+
});
|
|
216
|
+
return { ...raw, entities };
|
|
217
|
+
}
|
|
218
|
+
/** A prefab's components with overrides merged in, one level deep. `null` drops a component. */
|
|
219
|
+
export function mergeComponents(base, overrides) {
|
|
220
|
+
const merged = {};
|
|
221
|
+
for (const [name, value] of Object.entries(base))
|
|
222
|
+
if (isObject(value))
|
|
223
|
+
merged[name] = structuredClone(value);
|
|
224
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
225
|
+
if (value === null)
|
|
226
|
+
delete merged[name];
|
|
227
|
+
else if (isObject(value) && isObject(merged[name]))
|
|
228
|
+
merged[name] = { ...merged[name], ...structuredClone(value) };
|
|
229
|
+
else
|
|
230
|
+
merged[name] = structuredClone(value);
|
|
231
|
+
}
|
|
232
|
+
return merged;
|
|
233
|
+
}
|
|
152
234
|
function numberOr(value, fallback) {
|
|
153
235
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
154
236
|
}
|
|
@@ -177,6 +259,37 @@ function validateFields(fields, value, path, owner, where, error, warn, ignore =
|
|
|
177
259
|
}
|
|
178
260
|
}
|
|
179
261
|
}
|
|
262
|
+
function validateFonts(fonts, error) {
|
|
263
|
+
const example = 'Use "fonts": { "Orbitron": "google", "Pixel": "assets/pixel.woff2" }.';
|
|
264
|
+
if (!isObject(fonts))
|
|
265
|
+
return void error("E_FONTS_NOT_OBJECT", "fonts", '"fonts" must map font family names to "google" or a font file.', example);
|
|
266
|
+
for (const [family, src] of Object.entries(fonts)) {
|
|
267
|
+
const ok = typeof src === "string" && (src === "google" || FONT_EXTENSIONS.some((ext) => src.toLowerCase().endsWith(ext)));
|
|
268
|
+
if (!ok) {
|
|
269
|
+
error("E_INVALID_FONT", `fonts.${family}`, `fonts.${family} must be "google" or a font file (${FONT_EXTENSIONS.join(", ")}), got ${describe(src)}.`, example);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function validatePostFx(postfx, error) {
|
|
274
|
+
const example = 'Use "postfx": { "shader": "shaders/crt.frag", "uniforms": { "strength": 0.5 } }.';
|
|
275
|
+
if (!isObject(postfx))
|
|
276
|
+
return void error("E_POSTFX_NOT_OBJECT", "postfx", '"postfx" must be an object.', example);
|
|
277
|
+
if (typeof postfx.shader !== "string" || postfx.shader === "") {
|
|
278
|
+
error("E_POSTFX_SHADER", "postfx.shader", "postfx.shader must be the path of a GLSL fragment shader.", example);
|
|
279
|
+
}
|
|
280
|
+
if (postfx.uniforms !== undefined) {
|
|
281
|
+
if (!isObject(postfx.uniforms))
|
|
282
|
+
return void error("E_POSTFX_UNIFORMS", "postfx.uniforms", "postfx.uniforms must map names to numbers.", example);
|
|
283
|
+
for (const [name, value] of Object.entries(postfx.uniforms)) {
|
|
284
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || name.startsWith("u_")) {
|
|
285
|
+
error("E_POSTFX_UNIFORMS", `postfx.uniforms.${name}`, `"${name}" is not a usable uniform name. Use a GLSL identifier that doesn't start with "u_" (reserved for the engine).`);
|
|
286
|
+
}
|
|
287
|
+
else if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
288
|
+
error("E_POSTFX_UNIFORMS", `postfx.uniforms.${name}`, `postfx.uniforms.${name} must be a number, got ${describe(value)}.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
180
293
|
function validateEffects(effects, error, warn) {
|
|
181
294
|
if (!isObject(effects)) {
|
|
182
295
|
error("E_EFFECTS_NOT_OBJECT", "effects", '"effects" must map event names to effects.', 'Use "effects": { "collect": { "sound": "coin" } }.');
|
package/dist/world.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Body, type Camera, type Collectible, type Emitter, type Goal, type Hazard, type Patrol, type Platformer, type ScriptComponent, type Sprite, type Transform } from "./components.ts";
|
|
2
|
-
import { type Script, type ScriptModules } from "./script.ts";
|
|
1
|
+
import { type Body, type Camera, type Collectible, type Emitter, type Goal, type Hazard, type Patrol, type Platformer, type ScriptComponent, type Sprite, type Topdown, type Transform } from "./components.ts";
|
|
2
|
+
import { type DrawContext, type Script, type ScriptModules } from "./script.ts";
|
|
3
3
|
import { type Action, type Diagnostic, type SceneDef } from "./types.ts";
|
|
4
4
|
export interface BodyState extends Body {
|
|
5
5
|
grounded: boolean;
|
|
@@ -11,8 +11,6 @@ export interface PlatformerState extends Platformer {
|
|
|
11
11
|
/** True from a player-initiated jump until the apex, so only those jumps are cut short on release. */
|
|
12
12
|
jumping: boolean;
|
|
13
13
|
bufferTimer: number;
|
|
14
|
-
spawnX: number;
|
|
15
|
-
spawnY: number;
|
|
16
14
|
}
|
|
17
15
|
export interface PatrolState extends Patrol {
|
|
18
16
|
originX: number;
|
|
@@ -35,10 +33,17 @@ export interface Entity {
|
|
|
35
33
|
body?: BodyState;
|
|
36
34
|
sprite?: Sprite;
|
|
37
35
|
platformer?: PlatformerState;
|
|
36
|
+
topdown?: Topdown;
|
|
37
|
+
/** Where a player respawns after dying. */
|
|
38
|
+
spawn?: {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
};
|
|
38
42
|
collectible?: Collectible;
|
|
39
43
|
patrol?: PatrolState;
|
|
40
44
|
hazard?: Hazard;
|
|
41
45
|
goal?: Goal;
|
|
46
|
+
checkpoint?: Record<string, never>;
|
|
42
47
|
emitter?: Emitter;
|
|
43
48
|
camera?: Camera;
|
|
44
49
|
}
|
|
@@ -72,6 +77,22 @@ export type GameEvent = {
|
|
|
72
77
|
type: "win";
|
|
73
78
|
entity: string;
|
|
74
79
|
goal: string;
|
|
80
|
+
} | {
|
|
81
|
+
type: "bounce";
|
|
82
|
+
entity: string;
|
|
83
|
+
other: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: "checkpoint";
|
|
86
|
+
entity: string;
|
|
87
|
+
checkpoint: string;
|
|
88
|
+
} | {
|
|
89
|
+
type: "gameover";
|
|
90
|
+
entity: string;
|
|
91
|
+
cause: string;
|
|
92
|
+
} | {
|
|
93
|
+
type: "goto";
|
|
94
|
+
entity: string;
|
|
95
|
+
scene: string;
|
|
75
96
|
}
|
|
76
97
|
/** Fired by a script with world.emit(name, data). */
|
|
77
98
|
| {
|
|
@@ -92,6 +113,12 @@ export interface EntitySnapshot {
|
|
|
92
113
|
export interface WorldOptions {
|
|
93
114
|
/** Loaded script modules, keyed by the `src` path used in the scene. */
|
|
94
115
|
scripts?: ScriptModules;
|
|
116
|
+
/** Values carried over from the previous scene, so a run keeps its score and lives. */
|
|
117
|
+
carry?: {
|
|
118
|
+
score?: number;
|
|
119
|
+
deaths?: number;
|
|
120
|
+
lives?: number;
|
|
121
|
+
};
|
|
95
122
|
}
|
|
96
123
|
export interface WorldSnapshot {
|
|
97
124
|
frame: number;
|
|
@@ -99,6 +126,9 @@ export interface WorldSnapshot {
|
|
|
99
126
|
score: number;
|
|
100
127
|
deaths: number;
|
|
101
128
|
won: boolean;
|
|
129
|
+
lost: boolean;
|
|
130
|
+
/** Lives left; undefined when the scene has unlimited retries. */
|
|
131
|
+
lives?: number;
|
|
102
132
|
entities: Record<string, EntitySnapshot>;
|
|
103
133
|
}
|
|
104
134
|
export declare class SceneValidationError extends Error {
|
|
@@ -122,6 +152,14 @@ export declare class World {
|
|
|
122
152
|
deaths: number;
|
|
123
153
|
/** Set when a player reaches a goal. The simulation stops changing after that. */
|
|
124
154
|
won: boolean;
|
|
155
|
+
/** Set when the game is over: out of lives, or a script called world.lose(). */
|
|
156
|
+
lost: boolean;
|
|
157
|
+
/** Lives left, or undefined for unlimited retries. */
|
|
158
|
+
lives: number | undefined;
|
|
159
|
+
/** The scene to play next (a won goal's `next`, or world.goto). Hosts that can load scenes move on to it. */
|
|
160
|
+
exit: string | undefined;
|
|
161
|
+
private readonly prefabs;
|
|
162
|
+
private readonly reached;
|
|
125
163
|
private previousInput;
|
|
126
164
|
private readonly scripts;
|
|
127
165
|
private seed;
|
|
@@ -131,10 +169,14 @@ export declare class World {
|
|
|
131
169
|
private scriptInput;
|
|
132
170
|
/** The script hook currently running, for error messages and event attribution. */
|
|
133
171
|
private current;
|
|
172
|
+
/** True while a draw hook runs: the game can't be changed then. */
|
|
173
|
+
private drawing;
|
|
134
174
|
constructor(scene: SceneDef, options?: WorldOptions);
|
|
135
175
|
/** Validates raw scene JSON and builds a world, throwing on errors. */
|
|
136
176
|
static fromJSON(raw: unknown, options?: WorldOptions): World;
|
|
137
177
|
get time(): number;
|
|
178
|
+
/** True once nothing more happens in this scene: won, lost, or moving on to another scene. */
|
|
179
|
+
get over(): boolean;
|
|
138
180
|
get(id: string): Entity | undefined;
|
|
139
181
|
/** Advances one frame with the given actions held down. */
|
|
140
182
|
step(input: ReadonlySet<Action>): void;
|
|
@@ -146,6 +188,16 @@ export declare class World {
|
|
|
146
188
|
/** Calls `touch` on scripted entities that started overlapping or touching another body this frame. */
|
|
147
189
|
private updateTouches;
|
|
148
190
|
private callHook;
|
|
191
|
+
/**
|
|
192
|
+
* Runs an entity's `draw` or `drawUI` hook. Called by the renderer, never by the
|
|
193
|
+
* simulation. `self` and `world` are read-only for the duration.
|
|
194
|
+
*/
|
|
195
|
+
callDrawHook(entity: Entity, hook: "draw" | "drawUI", extras: Omit<DrawContext, "self" | "world" | "params">): void;
|
|
196
|
+
/** True when any live entity's script has the given hook. */
|
|
197
|
+
hasHook(hook: "draw" | "drawUI"): boolean;
|
|
198
|
+
private run;
|
|
199
|
+
/** Draw hooks only paint, so the browser can't drift from what tests verified. */
|
|
200
|
+
private guard;
|
|
149
201
|
/** An error caused by how a script used the API, reported against the running script. */
|
|
150
202
|
private misuse;
|
|
151
203
|
/** A stable script-facing view of an entity. */
|
|
@@ -156,14 +208,25 @@ export declare class World {
|
|
|
156
208
|
private spawn;
|
|
157
209
|
/** mulberry32, seeded by world.seed. */
|
|
158
210
|
private random;
|
|
211
|
+
/** Entities the player controls: platformers and topdown movers. */
|
|
159
212
|
private players;
|
|
213
|
+
private updateTopdown;
|
|
160
214
|
private updatePlatformers;
|
|
161
215
|
/** Points each patrolling body toward the end of its path it is heading for. */
|
|
162
216
|
private updatePatrols;
|
|
163
217
|
private updateBodies;
|
|
218
|
+
/**
|
|
219
|
+
* The velocity along one axis after hitting `solid`: 0, or reversed and scaled by `bounce`.
|
|
220
|
+
* Slow impacts stop instead, so resting bodies don't jitter.
|
|
221
|
+
*/
|
|
222
|
+
private rebound;
|
|
223
|
+
private clampToWorld;
|
|
164
224
|
private updateCollectibles;
|
|
165
225
|
private updateHazards;
|
|
166
226
|
private updateGoals;
|
|
227
|
+
/** A touched checkpoint becomes the player's respawn point, standing on the checkpoint's bottom edge. */
|
|
228
|
+
private updateCheckpoints;
|
|
229
|
+
private gameOver;
|
|
167
230
|
/** Sends a player back to where it started. Collected items stay collected. */
|
|
168
231
|
private kill;
|
|
169
232
|
}
|