@netforge/engine 0.0.0-stage → 0.1.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/GAME-DESIGN.md +195 -0
- package/LICENSE +21 -0
- package/NOTICE +30 -0
- package/README.md +147 -2
- package/dist/api/appearance.d.ts +37 -0
- package/dist/api/appearance.js +41 -0
- package/dist/api/cards.d.ts +105 -0
- package/dist/api/cards.js +133 -0
- package/dist/api/collector.d.ts +114 -0
- package/dist/api/collector.js +204 -0
- package/dist/api/dungeon.d.ts +58 -0
- package/dist/api/dungeon.js +216 -0
- package/dist/api/harness.d.ts +149 -0
- package/dist/api/harness.js +549 -0
- package/dist/api/idle.d.ts +71 -0
- package/dist/api/idle.js +104 -0
- package/dist/api/race-harness.d.ts +3 -0
- package/dist/api/race-harness.js +148 -0
- package/dist/api/registry.d.ts +241 -0
- package/dist/api/registry.js +394 -0
- package/dist/api/scheduler.d.ts +56 -0
- package/dist/api/scheduler.js +160 -0
- package/dist/api/schemas.d.ts +958 -0
- package/dist/api/schemas.js +859 -0
- package/dist/api/tower.d.ts +127 -0
- package/dist/api/tower.js +183 -0
- package/dist/game/adapter.d.ts +69 -0
- package/dist/game/adapter.js +341 -0
- package/dist/game/arena-bot.d.ts +66 -0
- package/dist/game/arena-bot.js +188 -0
- package/dist/game/bot.d.ts +95 -0
- package/dist/game/bot.js +285 -0
- package/dist/game/compile.d.ts +97 -0
- package/dist/game/compile.js +476 -0
- package/dist/game/director.d.ts +42 -0
- package/dist/game/director.js +179 -0
- package/dist/game/earn-source.d.ts +122 -0
- package/dist/game/earn-source.js +386 -0
- package/dist/game/earn.d.ts +49 -0
- package/dist/game/earn.js +55 -0
- package/dist/game/index.d.ts +37 -0
- package/dist/game/index.js +25 -0
- package/dist/game/platformer.d.ts +156 -0
- package/dist/game/platformer.js +619 -0
- package/dist/game/profile.d.ts +97 -0
- package/dist/game/profile.js +140 -0
- package/dist/game/race.d.ts +177 -0
- package/dist/game/race.js +386 -0
- package/dist/game/racer.d.ts +98 -0
- package/dist/game/racer.js +379 -0
- package/dist/game/runner.d.ts +286 -0
- package/dist/game/runner.js +824 -0
- package/dist/game/threats.d.ts +23 -0
- package/dist/game/threats.js +49 -0
- package/dist/game/view.d.ts +47 -0
- package/dist/game/view.js +123 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +24 -0
- package/dist/net/bits.d.ts +33 -0
- package/dist/net/bits.js +135 -0
- package/dist/net/client.d.ts +75 -0
- package/dist/net/client.js +151 -0
- package/dist/net/index.d.ts +23 -0
- package/dist/net/index.js +23 -0
- package/dist/net/input.d.ts +49 -0
- package/dist/net/input.js +112 -0
- package/dist/net/interpolation.d.ts +37 -0
- package/dist/net/interpolation.js +97 -0
- package/dist/net/netsim.d.ts +22 -0
- package/dist/net/netsim.js +52 -0
- package/dist/net/prediction.d.ts +31 -0
- package/dist/net/prediction.js +101 -0
- package/dist/net/profile-client.d.ts +75 -0
- package/dist/net/profile-client.js +173 -0
- package/dist/net/protocol.d.ts +208 -0
- package/dist/net/protocol.js +8 -0
- package/dist/net/room-client.d.ts +57 -0
- package/dist/net/room-client.js +144 -0
- package/dist/net/schema.d.ts +78 -0
- package/dist/net/schema.js +121 -0
- package/dist/net/sim-adapter.d.ts +134 -0
- package/dist/net/sim-adapter.js +22 -0
- package/dist/net/snapshot.d.ts +61 -0
- package/dist/net/snapshot.js +301 -0
- package/dist/rules/adapter.d.ts +53 -0
- package/dist/rules/adapter.js +261 -0
- package/dist/rules/cards.d.ts +115 -0
- package/dist/rules/cards.js +547 -0
- package/dist/rules/collector.d.ts +161 -0
- package/dist/rules/collector.js +732 -0
- package/dist/rules/genres.d.ts +13 -0
- package/dist/rules/genres.js +38 -0
- package/dist/rules/harness.d.ts +16 -0
- package/dist/rules/harness.js +127 -0
- package/dist/rules/idle.d.ts +115 -0
- package/dist/rules/idle.js +370 -0
- package/dist/rules/index.d.ts +16 -0
- package/dist/rules/index.js +14 -0
- package/dist/rules/kit.d.ts +171 -0
- package/dist/rules/kit.js +88 -0
- package/dist/rules/tower.d.ts +214 -0
- package/dist/rules/tower.js +655 -0
- package/dist/runtime/arena.d.ts +86 -0
- package/dist/runtime/arena.js +269 -0
- package/dist/runtime/assets.d.ts +26 -0
- package/dist/runtime/assets.js +35 -0
- package/dist/runtime/camera.d.ts +113 -0
- package/dist/runtime/camera.js +247 -0
- package/dist/runtime/crowd.d.ts +18 -0
- package/dist/runtime/crowd.js +62 -0
- package/dist/runtime/feel/audio.d.ts +150 -0
- package/dist/runtime/feel/audio.js +327 -0
- package/dist/runtime/feel/clock.d.ts +52 -0
- package/dist/runtime/feel/clock.js +80 -0
- package/dist/runtime/feel/flash.d.ts +28 -0
- package/dist/runtime/feel/flash.js +60 -0
- package/dist/runtime/feel/index.d.ts +27 -0
- package/dist/runtime/feel/index.js +16 -0
- package/dist/runtime/feel/kit.d.ts +159 -0
- package/dist/runtime/feel/kit.js +226 -0
- package/dist/runtime/feel/knockback.d.ts +41 -0
- package/dist/runtime/feel/knockback.js +70 -0
- package/dist/runtime/feel/noise.d.ts +6 -0
- package/dist/runtime/feel/noise.js +30 -0
- package/dist/runtime/feel/numbers.d.ts +91 -0
- package/dist/runtime/feel/numbers.js +172 -0
- package/dist/runtime/feel/rumble.d.ts +40 -0
- package/dist/runtime/feel/rumble.js +58 -0
- package/dist/runtime/feel/sound.d.ts +96 -0
- package/dist/runtime/feel/sound.js +176 -0
- package/dist/runtime/feel/spring.d.ts +42 -0
- package/dist/runtime/feel/spring.js +95 -0
- package/dist/runtime/feel/trauma.d.ts +48 -0
- package/dist/runtime/feel/trauma.js +76 -0
- package/dist/runtime/feel/tween.d.ts +66 -0
- package/dist/runtime/feel/tween.js +180 -0
- package/dist/runtime/feel/types.d.ts +30 -0
- package/dist/runtime/feel/types.js +5 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.js +26 -0
- package/dist/runtime/input.d.ts +38 -0
- package/dist/runtime/input.js +159 -0
- package/dist/runtime/interpolate.d.ts +6 -0
- package/dist/runtime/interpolate.js +13 -0
- package/dist/runtime/render/animation.d.ts +63 -0
- package/dist/runtime/render/animation.js +128 -0
- package/dist/runtime/render/atmosphere.d.ts +32 -0
- package/dist/runtime/render/atmosphere.js +59 -0
- package/dist/runtime/render/crowd-motion.d.ts +68 -0
- package/dist/runtime/render/crowd-motion.js +163 -0
- package/dist/runtime/render/decals.d.ts +64 -0
- package/dist/runtime/render/decals.js +222 -0
- package/dist/runtime/render/highlight.d.ts +29 -0
- package/dist/runtime/render/highlight.js +110 -0
- package/dist/runtime/render/index.d.ts +35 -0
- package/dist/runtime/render/index.js +22 -0
- package/dist/runtime/render/lighting.d.ts +50 -0
- package/dist/runtime/render/lighting.js +142 -0
- package/dist/runtime/render/particles.d.ts +117 -0
- package/dist/runtime/render/particles.js +289 -0
- package/dist/runtime/render/post.d.ts +92 -0
- package/dist/runtime/render/post.js +191 -0
- package/dist/runtime/render/presets.d.ts +57 -0
- package/dist/runtime/render/presets.js +116 -0
- package/dist/runtime/render/quality.d.ts +113 -0
- package/dist/runtime/render/quality.js +180 -0
- package/dist/runtime/render/rig.d.ts +29 -0
- package/dist/runtime/render/rig.js +107 -0
- package/dist/runtime/render/sprite.d.ts +34 -0
- package/dist/runtime/render/sprite.js +58 -0
- package/dist/runtime/render/stack.d.ts +74 -0
- package/dist/runtime/render/stack.js +132 -0
- package/dist/runtime/render/vat.d.ts +78 -0
- package/dist/runtime/render/vat.js +219 -0
- package/dist/runtime/renderer.d.ts +19 -0
- package/dist/runtime/renderer.js +18 -0
- package/dist/runtime/stats.d.ts +11 -0
- package/dist/runtime/stats.js +29 -0
- package/dist/runtime/styles/handdrawn/ground.d.ts +11 -0
- package/dist/runtime/styles/handdrawn/ground.js +282 -0
- package/dist/runtime/styles/handdrawn/index.d.ts +4 -0
- package/dist/runtime/styles/handdrawn/index.js +94 -0
- package/dist/runtime/styles/handdrawn/ink.d.ts +31 -0
- package/dist/runtime/styles/handdrawn/ink.js +68 -0
- package/dist/runtime/styles/handdrawn/looks.d.ts +18 -0
- package/dist/runtime/styles/handdrawn/looks.js +42 -0
- package/dist/runtime/styles/handdrawn/paint.d.ts +59 -0
- package/dist/runtime/styles/handdrawn/paint.js +129 -0
- package/dist/runtime/styles/handdrawn/palette.d.ts +49 -0
- package/dist/runtime/styles/handdrawn/palette.js +70 -0
- package/dist/runtime/styles/handdrawn/textures.d.ts +27 -0
- package/dist/runtime/styles/handdrawn/textures.js +150 -0
- package/dist/runtime/styles/handdrawn/vfx.d.ts +14 -0
- package/dist/runtime/styles/handdrawn/vfx.js +73 -0
- package/dist/runtime/styles/index.d.ts +9 -0
- package/dist/runtime/styles/index.js +32 -0
- package/dist/runtime/styles/kit.d.ts +193 -0
- package/dist/runtime/styles/kit.js +1 -0
- package/dist/runtime/styles/pixel/index.d.ts +46 -0
- package/dist/runtime/styles/pixel/index.js +167 -0
- package/dist/runtime/styles/ps1/index.d.ts +29 -0
- package/dist/runtime/styles/ps1/index.js +41 -0
- package/dist/runtime/styles/ps1/lights.d.ts +47 -0
- package/dist/runtime/styles/ps1/lights.js +146 -0
- package/dist/runtime/styles/ps1/materials.d.ts +62 -0
- package/dist/runtime/styles/ps1/materials.js +285 -0
- package/dist/runtime/styles/ps1/pipeline.d.ts +52 -0
- package/dist/runtime/styles/ps1/pipeline.js +111 -0
- package/dist/runtime/styles/ps1/surfaces.d.ts +44 -0
- package/dist/runtime/styles/ps1/surfaces.js +228 -0
- package/dist/runtime/styles/toon/index.d.ts +5 -0
- package/dist/runtime/styles/toon/index.js +178 -0
- package/dist/runtime/styles/toon/ink.d.ts +39 -0
- package/dist/runtime/styles/toon/ink.js +73 -0
- package/dist/runtime/styles/toon/look.d.ts +10 -0
- package/dist/runtime/styles/toon/look.js +112 -0
- package/dist/runtime/styles/toon/material.d.ts +74 -0
- package/dist/runtime/styles/toon/material.js +208 -0
- package/dist/runtime/styles/toon/normals.d.ts +26 -0
- package/dist/runtime/styles/toon/normals.js +127 -0
- package/dist/runtime/styles/toon/shadows.d.ts +24 -0
- package/dist/runtime/styles/toon/shadows.js +42 -0
- package/dist/runtime/styles/toon/vfx.d.ts +15 -0
- package/dist/runtime/styles/toon/vfx.js +47 -0
- package/dist/runtime/styles/view.d.ts +35 -0
- package/dist/runtime/styles/view.js +34 -0
- package/dist/runtime/styles/voxel/core.d.ts +114 -0
- package/dist/runtime/styles/voxel/core.js +577 -0
- package/dist/runtime/styles/voxel/geometry.d.ts +27 -0
- package/dist/runtime/styles/voxel/geometry.js +40 -0
- package/dist/runtime/styles/voxel/index.d.ts +21 -0
- package/dist/runtime/styles/voxel/index.js +23 -0
- package/dist/runtime/styles/voxel/kit.d.ts +6 -0
- package/dist/runtime/styles/voxel/kit.js +11 -0
- package/dist/runtime/styles/voxel/look.d.ts +10 -0
- package/dist/runtime/styles/voxel/look.js +16 -0
- package/dist/runtime/styles/voxel/props.d.ts +9 -0
- package/dist/runtime/styles/voxel/props.js +52 -0
- package/dist/runtime/styles/voxel/shapes.d.ts +41 -0
- package/dist/runtime/styles/voxel/shapes.js +83 -0
- package/dist/runtime/styles/voxel/world.d.ts +49 -0
- package/dist/runtime/styles/voxel/world.js +422 -0
- package/llms.txt +183 -0
- package/package.json +82 -3
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tower defense declarations (game({ template: "tower-defense" })). The
|
|
3
|
+
* template already runs the board, the path, the waves, targeting, gold,
|
|
4
|
+
* co-op and the referee; a game declares its board, its towers and its
|
|
5
|
+
* waves. Creeps are ordinary prefab(...)s: model, health, speed (m/s),
|
|
6
|
+
* value (the gold bounty), damage (lives a leak costs, default 1) and boss.
|
|
7
|
+
*
|
|
8
|
+
* towerBoard({ size: [18, 12], path: [[0, 2], [7, 2], [7, 9], [17, 9]], lives: 20, gold: 120 });
|
|
9
|
+
* tower("bolt_post", { model: "style.lowpoly.turret", kind: "bolt", cost: 40, damage: 9, rate: 1.6, range: 3,
|
|
10
|
+
* levels: [{ cost: 45, damage: 15 }, { cost: 80, damage: 26, range: 3.6 }] });
|
|
11
|
+
* waves({ total: 20, groups: [{ prefab: "rustling", count: 8, grow: 1.5, gap: 0.8 }] });
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import type { GameManifest } from "./registry.js";
|
|
15
|
+
export declare const towerBoardSchema: z.ZodObject<{
|
|
16
|
+
size: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
17
|
+
path: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
18
|
+
blocked: z.ZodDefault<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
19
|
+
lives: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
gold: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
prep: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
breather: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
share: z.ZodDefault<z.ZodNumber>;
|
|
24
|
+
waveGold: z.ZodDefault<z.ZodObject<{
|
|
25
|
+
base: z.ZodNumber;
|
|
26
|
+
perWave: z.ZodNumber;
|
|
27
|
+
}, z.core.$strict>>;
|
|
28
|
+
refund: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
export declare const TOWER_KINDS: readonly ["bolt", "splash", "frost", "chain"];
|
|
31
|
+
/**
|
|
32
|
+
* A tower. kind "bolt" hits one creep; "splash" hits every creep within
|
|
33
|
+
* `radius` tiles of its target; "frost" hits and slows (`slow` 0..0.9 for
|
|
34
|
+
* `slowSeconds`); "chain" jumps to `chains` creeps in a row, 30% weaker a
|
|
35
|
+
* jump. Damage per hit, rate in shots a second, range and radius in tiles.
|
|
36
|
+
* `levels` are the upgrades, each one's cost and what it changes.
|
|
37
|
+
*/
|
|
38
|
+
export declare const towerSchema: z.ZodObject<{
|
|
39
|
+
model: z.ZodString;
|
|
40
|
+
kind: z.ZodEnum<{
|
|
41
|
+
bolt: "bolt";
|
|
42
|
+
chain: "chain";
|
|
43
|
+
splash: "splash";
|
|
44
|
+
frost: "frost";
|
|
45
|
+
}>;
|
|
46
|
+
cost: z.ZodNumber;
|
|
47
|
+
damage: z.ZodNumber;
|
|
48
|
+
rate: z.ZodNumber;
|
|
49
|
+
range: z.ZodNumber;
|
|
50
|
+
radius: z.ZodDefault<z.ZodNumber>;
|
|
51
|
+
slow: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
slowSeconds: z.ZodDefault<z.ZodNumber>;
|
|
53
|
+
chains: z.ZodDefault<z.ZodNumber>;
|
|
54
|
+
levels: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
55
|
+
cost: z.ZodNumber;
|
|
56
|
+
damage: z.ZodOptional<z.ZodNumber>;
|
|
57
|
+
rate: z.ZodOptional<z.ZodNumber>;
|
|
58
|
+
range: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
radius: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
slow: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
chains: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
}, z.core.$strict>>>;
|
|
63
|
+
feel: z.ZodOptional<z.ZodObject<{
|
|
64
|
+
shake: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
hitStop: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
particles: z.ZodOptional<z.ZodEnum<{
|
|
67
|
+
sparks: "sparks";
|
|
68
|
+
burst: "burst";
|
|
69
|
+
embers: "embers";
|
|
70
|
+
smoke: "smoke";
|
|
71
|
+
hitSpark: "hitSpark";
|
|
72
|
+
dust: "dust";
|
|
73
|
+
magic: "magic";
|
|
74
|
+
}>>;
|
|
75
|
+
color: z.ZodOptional<z.ZodString>;
|
|
76
|
+
decal: z.ZodOptional<z.ZodEnum<{
|
|
77
|
+
none: "none";
|
|
78
|
+
scorch: "scorch";
|
|
79
|
+
splat: "splat";
|
|
80
|
+
ring: "ring";
|
|
81
|
+
}>>;
|
|
82
|
+
sound: z.ZodOptional<z.ZodEnum<{
|
|
83
|
+
none: "none";
|
|
84
|
+
hit: "hit";
|
|
85
|
+
crunch: "crunch";
|
|
86
|
+
boom: "boom";
|
|
87
|
+
zap: "zap";
|
|
88
|
+
chime: "chime";
|
|
89
|
+
thud: "thud";
|
|
90
|
+
}>>;
|
|
91
|
+
flash: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
+
}, z.core.$strict>>;
|
|
93
|
+
}, z.core.$strict>;
|
|
94
|
+
export declare const wavesSchema: z.ZodObject<{
|
|
95
|
+
total: z.ZodNumber;
|
|
96
|
+
groups: z.ZodArray<z.ZodObject<{
|
|
97
|
+
prefab: z.ZodString;
|
|
98
|
+
from: z.ZodDefault<z.ZodNumber>;
|
|
99
|
+
until: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
every: z.ZodDefault<z.ZodNumber>;
|
|
101
|
+
count: z.ZodNumber;
|
|
102
|
+
grow: z.ZodDefault<z.ZodNumber>;
|
|
103
|
+
gap: z.ZodDefault<z.ZodNumber>;
|
|
104
|
+
delay: z.ZodDefault<z.ZodNumber>;
|
|
105
|
+
}, z.core.$strict>>;
|
|
106
|
+
health: z.ZodDefault<z.ZodNumber>;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
export type TowerBoardInput = z.input<typeof towerBoardSchema>;
|
|
109
|
+
export type TowerBoardDef = z.output<typeof towerBoardSchema>;
|
|
110
|
+
export type TowerInput = z.input<typeof towerSchema>;
|
|
111
|
+
export type TowerDef = z.output<typeof towerSchema>;
|
|
112
|
+
export type WavesInput = z.input<typeof wavesSchema>;
|
|
113
|
+
export type WavesDef = z.output<typeof wavesSchema>;
|
|
114
|
+
/** The board: its size, the creeps' road, lives and starting gold (one per game). */
|
|
115
|
+
export declare function towerBoard(def: TowerBoardInput): TowerBoardDef;
|
|
116
|
+
/** A tower players build (at most 8). */
|
|
117
|
+
export declare function tower(id: string, def: TowerInput): TowerDef;
|
|
118
|
+
/** The waves (one per game). */
|
|
119
|
+
export declare function waves(def: WavesInput): WavesDef;
|
|
120
|
+
/** Store upgrade stats a tower defense game reads: tower damage, fire rate, range, and team lives. */
|
|
121
|
+
export declare const TOWER_STATS: readonly ["might", "haste", "area", "maxHealth"];
|
|
122
|
+
/** The tower defense content of a manifest, cross-checked (one line per issue). */
|
|
123
|
+
export declare function towerContent(m: GameManifest): {
|
|
124
|
+
board: TowerBoardDef;
|
|
125
|
+
towers: [string, TowerDef][];
|
|
126
|
+
waves: WavesDef;
|
|
127
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tower defense declarations (game({ template: "tower-defense" })). The
|
|
3
|
+
* template already runs the board, the path, the waves, targeting, gold,
|
|
4
|
+
* co-op and the referee; a game declares its board, its towers and its
|
|
5
|
+
* waves. Creeps are ordinary prefab(...)s: model, health, speed (m/s),
|
|
6
|
+
* value (the gold bounty), damage (lives a leak costs, default 1) and boss.
|
|
7
|
+
*
|
|
8
|
+
* towerBoard({ size: [18, 12], path: [[0, 2], [7, 2], [7, 9], [17, 9]], lives: 20, gold: 120 });
|
|
9
|
+
* tower("bolt_post", { model: "style.lowpoly.turret", kind: "bolt", cost: 40, damage: 9, rate: 1.6, range: 3,
|
|
10
|
+
* levels: [{ cost: 45, damage: 15 }, { cost: 80, damage: 26, range: 3.6 }] });
|
|
11
|
+
* waves({ total: 20, groups: [{ prefab: "rustling", count: 8, grow: 1.5, gap: 0.8 }] });
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { contentOf, declareContent } from "./registry.js";
|
|
15
|
+
import { assetIdSchema, feelSchema, GameDefinitionError, idSchema, parseOrThrow } from "./schemas.js";
|
|
16
|
+
const cell = z.tuple([z.number().int().min(0).max(39), z.number().int().min(0).max(39)]);
|
|
17
|
+
export const towerBoardSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
/** Tiles across and down. Each tile is 2 m. */
|
|
20
|
+
size: z.tuple([z.number().int().min(8).max(32), z.number().int().min(6).max(24)]),
|
|
21
|
+
/** The creeps' road: tile waypoints joined by straight runs (each step changes x or y, not both). Starts and ends on the board's edge. */
|
|
22
|
+
path: z.array(cell).min(2).max(24),
|
|
23
|
+
/** Tiles nobody can build on (rocks, ruins). */
|
|
24
|
+
blocked: z.array(cell).max(96).default([]),
|
|
25
|
+
/** Leaks the team can take before the run is lost. */
|
|
26
|
+
lives: z.number().int().min(1).max(200).default(20),
|
|
27
|
+
/** Gold each player starts a run with (gold is the run's own money, never coins). */
|
|
28
|
+
gold: z.number().int().min(0).max(100_000).default(100),
|
|
29
|
+
/** Seconds to build before the first wave. */
|
|
30
|
+
prep: z.number().min(3).max(120).default(15),
|
|
31
|
+
/** Seconds between waves (everyone pressing Ready skips the rest, for gold). */
|
|
32
|
+
breather: z.number().min(2).max(120).default(10),
|
|
33
|
+
/** Share of every bounty paid to each other player too (co-op), 0..1. */
|
|
34
|
+
share: z.number().min(0).max(1).default(0.25),
|
|
35
|
+
/** Gold each player gets for a cleared wave: base + perWave x wave. */
|
|
36
|
+
waveGold: z.object({ base: z.number().int().min(0).max(10_000), perWave: z.number().int().min(0).max(10_000) }).strict().default({ base: 10, perWave: 2 }),
|
|
37
|
+
/** Fraction of a tower's cost a sale returns. */
|
|
38
|
+
refund: z.number().min(0).max(1).default(0.6),
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
export const TOWER_KINDS = ["bolt", "splash", "frost", "chain"];
|
|
42
|
+
const towerLevelSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
cost: z.number().int().min(1).max(100_000),
|
|
45
|
+
damage: z.number().min(0).max(1_000_000).optional(),
|
|
46
|
+
rate: z.number().min(0.1).max(20).optional(),
|
|
47
|
+
range: z.number().min(1).max(12).optional(),
|
|
48
|
+
radius: z.number().min(0.2).max(4).optional(),
|
|
49
|
+
slow: z.number().min(0).max(0.9).optional(),
|
|
50
|
+
chains: z.number().int().min(1).max(8).optional(),
|
|
51
|
+
})
|
|
52
|
+
.strict();
|
|
53
|
+
/**
|
|
54
|
+
* A tower. kind "bolt" hits one creep; "splash" hits every creep within
|
|
55
|
+
* `radius` tiles of its target; "frost" hits and slows (`slow` 0..0.9 for
|
|
56
|
+
* `slowSeconds`); "chain" jumps to `chains` creeps in a row, 30% weaker a
|
|
57
|
+
* jump. Damage per hit, rate in shots a second, range and radius in tiles.
|
|
58
|
+
* `levels` are the upgrades, each one's cost and what it changes.
|
|
59
|
+
*/
|
|
60
|
+
export const towerSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
model: assetIdSchema,
|
|
63
|
+
kind: z.enum(TOWER_KINDS, { error: `tower kinds are: ${TOWER_KINDS.join(", ")}` }),
|
|
64
|
+
cost: z.number().int().min(1).max(100_000),
|
|
65
|
+
damage: z.number().min(0).max(1_000_000),
|
|
66
|
+
rate: z.number().min(0.1).max(20),
|
|
67
|
+
range: z.number().min(1).max(12),
|
|
68
|
+
radius: z.number().min(0.2).max(4).default(1),
|
|
69
|
+
slow: z.number().min(0).max(0.9).default(0),
|
|
70
|
+
slowSeconds: z.number().min(0.2).max(10).default(1.5),
|
|
71
|
+
chains: z.number().int().min(1).max(8).default(3),
|
|
72
|
+
levels: z.array(towerLevelSchema).max(4).default([]),
|
|
73
|
+
feel: feelSchema.optional(),
|
|
74
|
+
})
|
|
75
|
+
.strict();
|
|
76
|
+
const waveGroupSchema = z
|
|
77
|
+
.object({
|
|
78
|
+
/** A declared prefab (the creep). */
|
|
79
|
+
prefab: idSchema,
|
|
80
|
+
/** First wave it comes in (1-based). */
|
|
81
|
+
from: z.number().int().min(1).default(1),
|
|
82
|
+
/** Last wave it comes in. */
|
|
83
|
+
until: z.number().int().min(1).optional(),
|
|
84
|
+
/** Comes every this many waves from `from` (a boss every 5: from 5, every 5). */
|
|
85
|
+
every: z.number().int().min(1).default(1),
|
|
86
|
+
/** How many in its first wave. */
|
|
87
|
+
count: z.number().min(1).max(500),
|
|
88
|
+
/** How many more each later wave it comes in. */
|
|
89
|
+
grow: z.number().min(0).max(50).default(0),
|
|
90
|
+
/** Seconds between two of them. */
|
|
91
|
+
gap: z.number().min(0.05).max(10).default(0.8),
|
|
92
|
+
/** Seconds into the wave the group starts. */
|
|
93
|
+
delay: z.number().min(0).max(60).default(0),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
96
|
+
export const wavesSchema = z
|
|
97
|
+
.object({
|
|
98
|
+
/** Waves to clear to win the run. */
|
|
99
|
+
total: z.number().int().min(1).max(100),
|
|
100
|
+
groups: z.array(waveGroupSchema).min(1).max(12),
|
|
101
|
+
/** Creep health grows by this much a wave: health x (1 + health x (wave - 1)). */
|
|
102
|
+
health: z.number().min(0).max(2).default(0.1),
|
|
103
|
+
})
|
|
104
|
+
.strict();
|
|
105
|
+
/** The board: its size, the creeps' road, lives and starting gold (one per game). */
|
|
106
|
+
export function towerBoard(def) {
|
|
107
|
+
const parsed = parseOrThrow(towerBoardSchema, def, "towerBoard()");
|
|
108
|
+
declareContent("towerBoard", "board", parsed);
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
/** A tower players build (at most 8). */
|
|
112
|
+
export function tower(id, def) {
|
|
113
|
+
parseOrThrow(idSchema, id, "tower id");
|
|
114
|
+
const parsed = parseOrThrow(towerSchema, def, `tower("${id}")`);
|
|
115
|
+
declareContent("tower", id, parsed);
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
/** The waves (one per game). */
|
|
119
|
+
export function waves(def) {
|
|
120
|
+
const parsed = parseOrThrow(wavesSchema, def, "waves()");
|
|
121
|
+
declareContent("waves", "waves", parsed);
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
/** Store upgrade stats a tower defense game reads: tower damage, fire rate, range, and team lives. */
|
|
125
|
+
export const TOWER_STATS = ["might", "haste", "area", "maxHealth"];
|
|
126
|
+
/** The tower defense content of a manifest, cross-checked (one line per issue). */
|
|
127
|
+
export function towerContent(m) {
|
|
128
|
+
const issues = [];
|
|
129
|
+
const board = contentOf(m, "towerBoard")[0]?.[1];
|
|
130
|
+
const towers = contentOf(m, "tower");
|
|
131
|
+
const wv = contentOf(m, "waves")[0]?.[1];
|
|
132
|
+
if (!board)
|
|
133
|
+
issues.push("declare the board: towerBoard({ size, path, lives, gold })");
|
|
134
|
+
if (!wv)
|
|
135
|
+
issues.push("declare the waves: waves({ total, groups: [{ prefab, count, grow, gap }] })");
|
|
136
|
+
if (towers.length === 0)
|
|
137
|
+
issues.push('declare at least one tower: tower("bolt_post", { model, kind: "bolt", cost, damage, rate, range })');
|
|
138
|
+
if (towers.length > 8)
|
|
139
|
+
issues.push("at most 8 towers per game");
|
|
140
|
+
if (m.weapons.size > 0)
|
|
141
|
+
issues.push("weapon(...) is for the survivor and arena templates; a tower defense game declares tower(...)");
|
|
142
|
+
for (const [id, u] of m.upgrades)
|
|
143
|
+
if (!TOWER_STATS.includes(u.stat))
|
|
144
|
+
issues.push(`upgrade("${id}") raises ${u.stat}; tower defense reads ${TOWER_STATS.join(", ")} (tower damage, fire rate, range, team lives)`);
|
|
145
|
+
if (board) {
|
|
146
|
+
const [w, h] = board.size;
|
|
147
|
+
const inside = ([x, y]) => x >= 0 && y >= 0 && x < w && y < h;
|
|
148
|
+
for (const p of board.path)
|
|
149
|
+
if (!inside(p))
|
|
150
|
+
issues.push(`towerBoard path point [${p[0]}, ${p[1]}] is off the ${w} x ${h} board`);
|
|
151
|
+
for (let i = 1; i < board.path.length; i++) {
|
|
152
|
+
const a = board.path[i - 1];
|
|
153
|
+
const b = board.path[i];
|
|
154
|
+
if (a[0] !== b[0] && a[1] !== b[1])
|
|
155
|
+
issues.push(`towerBoard path steps from [${a[0]}, ${a[1]}] to [${b[0]}, ${b[1]}] diagonally; each step changes x or y, not both`);
|
|
156
|
+
if (a[0] === b[0] && a[1] === b[1])
|
|
157
|
+
issues.push(`towerBoard path repeats [${a[0]}, ${a[1]}]`);
|
|
158
|
+
}
|
|
159
|
+
const edge = ([x, y]) => x === 0 || y === 0 || x === w - 1 || y === h - 1;
|
|
160
|
+
const first = board.path[0];
|
|
161
|
+
const last = board.path[board.path.length - 1];
|
|
162
|
+
if (first && !edge(first))
|
|
163
|
+
issues.push("the path starts on the board's edge (where creeps come in)");
|
|
164
|
+
if (last && !edge(last))
|
|
165
|
+
issues.push("the path ends on the board's edge (the spire stands there)");
|
|
166
|
+
}
|
|
167
|
+
if (wv) {
|
|
168
|
+
for (const g of wv.groups) {
|
|
169
|
+
const p = m.prefabs.get(g.prefab);
|
|
170
|
+
if (!p)
|
|
171
|
+
issues.push(`waves group "${g.prefab}" is not a declared prefab`);
|
|
172
|
+
else if (p.kind === "pickup")
|
|
173
|
+
issues.push(`waves group "${g.prefab}" is a pickup, not a creep`);
|
|
174
|
+
if (g.until !== undefined && g.until < g.from)
|
|
175
|
+
issues.push(`waves group "${g.prefab}": until must not be before from`);
|
|
176
|
+
if (g.from > wv.total)
|
|
177
|
+
issues.push(`waves group "${g.prefab}" starts at wave ${g.from}, after the last wave (${wv.total})`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (issues.length > 0 || !board || !wv)
|
|
181
|
+
throw new GameDefinitionError("tower defense", issues);
|
|
182
|
+
return { board, towers, waves: wv };
|
|
183
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BeamShot, InputCommand, SimConfig, SimWorld } from "@netforge/engine-sim";
|
|
2
|
+
import type { EntitySchema } from "../net/schema.js";
|
|
3
|
+
import type { PlayerId, SimAdapter } from "../net/sim-adapter.js";
|
|
4
|
+
import type { CompiledGame } from "./compile.js";
|
|
5
|
+
import { GameRunner } from "./runner.js";
|
|
6
|
+
import type { RunnerOptions } from "./runner.js";
|
|
7
|
+
/** Field indices of the engine wire schema. */
|
|
8
|
+
export declare const F: {
|
|
9
|
+
readonly px: 0;
|
|
10
|
+
readonly py: 1;
|
|
11
|
+
readonly pz: 2;
|
|
12
|
+
readonly vx: 3;
|
|
13
|
+
readonly vy: 4;
|
|
14
|
+
readonly vz: 5;
|
|
15
|
+
readonly yaw: 6;
|
|
16
|
+
readonly health: 7;
|
|
17
|
+
readonly flags: 8;
|
|
18
|
+
readonly cooldown: 9;
|
|
19
|
+
readonly buttons: 10;
|
|
20
|
+
readonly timers: 11;
|
|
21
|
+
readonly type: 12;
|
|
22
|
+
readonly level: 13;
|
|
23
|
+
readonly xp: 14;
|
|
24
|
+
readonly weapons: 15;
|
|
25
|
+
readonly wcd: 16;
|
|
26
|
+
readonly stats: 17;
|
|
27
|
+
readonly choices: 18;
|
|
28
|
+
readonly bleed: 19;
|
|
29
|
+
readonly revive: 20;
|
|
30
|
+
readonly iframes: 21;
|
|
31
|
+
readonly dash: 22;
|
|
32
|
+
readonly ex: 23;
|
|
33
|
+
readonly ez: 24;
|
|
34
|
+
/** Players: rerolls used | reroll counter << 4 | keeper << 12 | revive buff ticks << 16 (prediction draws the same cards, moves at the buffed speed). */
|
|
35
|
+
readonly value: 25;
|
|
36
|
+
};
|
|
37
|
+
/** Units per metre of the crowd position fields (ex, ez): 1/16 m (6 cm) is below what a crowd shows on screen. */
|
|
38
|
+
export declare const CROWD_UNITS = 16;
|
|
39
|
+
/** Units per metre of player positions (px, py, pz): engine-sim's own fixed point. */
|
|
40
|
+
export declare const PLAYER_UNITS = 1024;
|
|
41
|
+
export declare const engineSchema: EntitySchema;
|
|
42
|
+
/** The world each side of the netcode holds. */
|
|
43
|
+
export interface EngineRoomWorld {
|
|
44
|
+
readonly sim: SimWorld;
|
|
45
|
+
readonly role: 'server' | 'client';
|
|
46
|
+
/** Server only, and only for games (the bare arena has none). */
|
|
47
|
+
readonly runner: GameRunner | null;
|
|
48
|
+
/** Arena: beams fired this tick by human players, awaiting the room's lag-compensated hit. */
|
|
49
|
+
readonly shots: Map<PlayerId, BeamShot>;
|
|
50
|
+
}
|
|
51
|
+
export interface EngineAdapterOptions {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
/** Content version (hot swap compares these). */
|
|
54
|
+
readonly version: string;
|
|
55
|
+
/** A compiled game, or omit for the bare week-2 arena (Fire = pulse). */
|
|
56
|
+
readonly game?: CompiledGame;
|
|
57
|
+
/** Arena-only sim config (ignored when `game` is set). */
|
|
58
|
+
readonly arena?: SimConfig;
|
|
59
|
+
/** Server-side runner options (measurement modes). */
|
|
60
|
+
readonly runner?: RunnerOptions;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the SimAdapter. One instance serves every room of a template; each
|
|
64
|
+
* room gets its own world from `createWorld`. `maxInputsPerTick: 1`:
|
|
65
|
+
* engine-sim steps exactly one command per player per tick.
|
|
66
|
+
*/
|
|
67
|
+
export declare function engineSimAdapter(opts: EngineAdapterOptions): SimAdapter<EngineRoomWorld, InputCommand>;
|
|
68
|
+
/** Free a world's physics memory (rooms call this on dispose). */
|
|
69
|
+
export declare function disposeEngineWorld(w: EngineRoomWorld): void;
|