@netforge/engine 0.0.0-stage → 0.2.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 +100 -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 +215 -0
- package/dist/net/protocol.js +10 -0
- package/dist/net/room-client.d.ts +65 -0
- package/dist/net/room-client.js +156 -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-full.txt +460 -0
- package/llms.txt +183 -0
- package/package.json +104 -3
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { GameDefinitionError } from "../api/schemas.js";
|
|
2
|
+
import { viewRadii, viewsFor } from "./view.js";
|
|
3
|
+
import { compilePlatformer, PLATFORMER_DEFAULTS } from "./platformer.js";
|
|
4
|
+
import { compileRacer, RACER_DEFAULTS } from "./racer.js";
|
|
5
|
+
/** Templates that run on the survivor machinery (co-op, auto-firing weapons, downs and revives, keepers). */
|
|
6
|
+
export const COOP_TEMPLATES = new Set(["survivor", "dungeon", "party"]);
|
|
7
|
+
/** Default enemy radius when a prefab gives none, m. */
|
|
8
|
+
const DEFAULT_RADIUS = 0.35;
|
|
9
|
+
/** Default contact damage when a prefab gives none. */
|
|
10
|
+
const DEFAULT_DAMAGE = 5;
|
|
11
|
+
/**
|
|
12
|
+
* Survivor-machinery defaults every game gets unless it says otherwise
|
|
13
|
+
* (GAME-DESIGN.md): a generous magnet, half a second of hit i-frames, a
|
|
14
|
+
* solo pause on level-up cards and a shove plus a second of i-frames when
|
|
15
|
+
* co-op cards open.
|
|
16
|
+
*/
|
|
17
|
+
export const SURVIVOR_DEFAULTS = {
|
|
18
|
+
magnet: 3.75,
|
|
19
|
+
hitIframes: 0.5,
|
|
20
|
+
choicePulseRadius: 2.5,
|
|
21
|
+
choicePulseIframes: 1,
|
|
22
|
+
/** Co-op XP: half of each gem to teammates within 20 m. */
|
|
23
|
+
share: { mode: "nearby", fraction: 0.5, radius: 20 },
|
|
24
|
+
/** The reviver's buff: 5 s of x1.25 damage and x1.15 speed. */
|
|
25
|
+
reviveBuff: { seconds: 5, damage: 1.25, speed: 1.15 },
|
|
26
|
+
/** Going down is short: 10 s downed (crawling), one free get-up a run, then back in 5 s beside a teammate. */
|
|
27
|
+
downed: { seconds: 10, crawl: 0.35, selfRevives: 1, respawn: 5 },
|
|
28
|
+
/** Getting up shoves enemies within this radius, m. */
|
|
29
|
+
risePulseRadius: 4,
|
|
30
|
+
};
|
|
31
|
+
/** Arena defaults: a 3 minute round, a 3 s respawn, 2 s spawn protection, heal cells last 20 s. */
|
|
32
|
+
export const ARENA_DEFAULTS = { roundSeconds: 180, respawnSeconds: 3, spawnProtectSeconds: 2, pickupSeconds: 20, pillarHeight: 2.4 };
|
|
33
|
+
const ARENA_KINDS = new Set(["blaster", "beam", "nova"]);
|
|
34
|
+
const SURVIVOR_KINDS = new Set(["nova", "bolt", "orbit", "cone", "chain", "aura", "meteor", "boomerang"]);
|
|
35
|
+
/** Default speed per weapon kind: m/s for projectiles, rad/s for orbits, jump reach (m) for chains. */
|
|
36
|
+
const DEFAULT_SPEED = { bolt: 20, boomerang: 14, orbit: 3, chain: 4 };
|
|
37
|
+
/** Base numbers of a weapon at level 1 (sim units). */
|
|
38
|
+
function baseLevel(w, tickRate) {
|
|
39
|
+
return {
|
|
40
|
+
damage: w.damage,
|
|
41
|
+
cooldownTicks: w.cooldown * tickRate,
|
|
42
|
+
range: w.range,
|
|
43
|
+
count: w.count,
|
|
44
|
+
speed: w.speed ?? DEFAULT_SPEED[w.kind] ?? 0,
|
|
45
|
+
pierce: w.pierce,
|
|
46
|
+
knockback: w.knockback,
|
|
47
|
+
spread: ((w.spread ?? (w.kind === "cone" ? 90 : 0)) * Math.PI) / 180,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Every level's numbers: level 1 is the weapon's own, each declared level applies its changes to the one before. */
|
|
51
|
+
export function levelTable(w, tickRate) {
|
|
52
|
+
const out = [baseLevel(w, tickRate)];
|
|
53
|
+
for (const change of w.levels ?? []) {
|
|
54
|
+
const prev = out[out.length - 1];
|
|
55
|
+
const next = { ...prev };
|
|
56
|
+
for (const [field, v] of Object.entries(change)) {
|
|
57
|
+
const key = field === "cooldown" ? "cooldownTicks" : field;
|
|
58
|
+
const before = next[key] ?? 0;
|
|
59
|
+
if (typeof v === "number")
|
|
60
|
+
next[key] = before + (field === "cooldown" ? v * tickRate : field === "spread" ? (v * Math.PI) / 180 : v);
|
|
61
|
+
else
|
|
62
|
+
next[key] = before * (1 + Number(v.slice(0, -1)) / 100);
|
|
63
|
+
}
|
|
64
|
+
next.cooldownTicks = Math.max(1, next.cooldownTicks ?? 1);
|
|
65
|
+
next.count = Math.max(1, Math.round(next.count ?? 1));
|
|
66
|
+
next.pierce = Math.max(0, Math.round(next.pierce ?? 0));
|
|
67
|
+
out.push(next);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** A declared ai as the sim's role ("tank" is a chaser: its weight is in its stats). */
|
|
72
|
+
function enemyAi(ai) {
|
|
73
|
+
if (ai === undefined || ai === "chase" || ai === "tank")
|
|
74
|
+
return "chase";
|
|
75
|
+
if (ai === "none" || ai === "idle")
|
|
76
|
+
return "idle";
|
|
77
|
+
return ai;
|
|
78
|
+
}
|
|
79
|
+
function compileAttack(id, p, tickRate, ref) {
|
|
80
|
+
const a = p.attack ?? {};
|
|
81
|
+
const t = (s) => (s === undefined ? undefined : Math.max(1, Math.round(s * tickRate)));
|
|
82
|
+
const summon = ref(id, "attack.summon", a.summon);
|
|
83
|
+
const windupTicks = t(a.windup);
|
|
84
|
+
const cooldownTicks = t(a.cooldown);
|
|
85
|
+
return {
|
|
86
|
+
...(a.damage !== undefined ? { damage: a.damage } : {}),
|
|
87
|
+
...(a.range !== undefined ? { range: a.range } : {}),
|
|
88
|
+
...(windupTicks !== undefined ? { windupTicks } : {}),
|
|
89
|
+
...(cooldownTicks !== undefined ? { cooldownTicks } : {}),
|
|
90
|
+
...(a.speed !== undefined ? { speed: a.speed } : {}),
|
|
91
|
+
...(a.radius !== undefined ? { radius: a.radius } : {}),
|
|
92
|
+
...(summon !== undefined ? { summon } : {}),
|
|
93
|
+
...(a.count !== undefined ? { count: a.count } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function splitOf(id, s, ref, issues) {
|
|
97
|
+
if (s.into === id) {
|
|
98
|
+
issues.push(`prefab "${id}" splits into itself, which never ends: split into a smaller prefab`);
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
const type = ref(id, "split.into", s.into);
|
|
102
|
+
return type === undefined ? {} : { split: { type, count: s.count } };
|
|
103
|
+
}
|
|
104
|
+
/** "enemy" or "pickup": explicit, else a prefab something drops is a pickup. */
|
|
105
|
+
export function prefabKind(m, id, p) {
|
|
106
|
+
if (p.kind)
|
|
107
|
+
return p.kind;
|
|
108
|
+
if (p.pickup)
|
|
109
|
+
return "pickup";
|
|
110
|
+
for (const other of m.prefabs.values())
|
|
111
|
+
if (other.drops.some((d) => (typeof d === "string" ? d : d.id) === id))
|
|
112
|
+
return "pickup";
|
|
113
|
+
return "enemy";
|
|
114
|
+
}
|
|
115
|
+
export function compileGame(m, opts = {}) {
|
|
116
|
+
const issues = [];
|
|
117
|
+
if (m.game.template === "platformer")
|
|
118
|
+
return compilePlatformerGame(m, issues);
|
|
119
|
+
if (m.game.template === "racer")
|
|
120
|
+
return compileRacerGame(m, issues);
|
|
121
|
+
const tickRate = m.game.tickRate ?? 30;
|
|
122
|
+
const enemyPrefabs = [];
|
|
123
|
+
const enemyTypes = [];
|
|
124
|
+
const pickups = [];
|
|
125
|
+
for (const [id, p] of m.prefabs) {
|
|
126
|
+
// Gems only: special pickups (p.pickup) have their own types.
|
|
127
|
+
if (prefabKind(m, id, p) === "pickup" && !p.pickup)
|
|
128
|
+
pickups.push({ id, value: p.value ?? 1 });
|
|
129
|
+
}
|
|
130
|
+
// Special pickups (heals, vacuums, bombs, chests): entity types PICKUP_SPECIAL + index.
|
|
131
|
+
const specialIds = [...m.prefabs].filter(([id, p]) => p.pickup && prefabKind(m, id, p) === "pickup").map(([id]) => id);
|
|
132
|
+
// Enemy type indices first: roles and bosses refer to other enemies (summons, splits).
|
|
133
|
+
const enemyIndex = new Map();
|
|
134
|
+
for (const [id, p] of m.prefabs)
|
|
135
|
+
if (prefabKind(m, id, p) === "enemy")
|
|
136
|
+
enemyIndex.set(id, enemyIndex.size);
|
|
137
|
+
const enemyRef = (from, field, to) => {
|
|
138
|
+
if (to === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const i = enemyIndex.get(to);
|
|
141
|
+
if (i === undefined)
|
|
142
|
+
issues.push(`prefab "${from}" ${field} "${to}", which is not an enemy prefab`);
|
|
143
|
+
return i;
|
|
144
|
+
};
|
|
145
|
+
for (const [id, p] of m.prefabs) {
|
|
146
|
+
if (prefabKind(m, id, p) !== "enemy")
|
|
147
|
+
continue;
|
|
148
|
+
let xp = 0;
|
|
149
|
+
const specialDrops = [];
|
|
150
|
+
for (const d of p.drops) {
|
|
151
|
+
const did = typeof d === "string" ? d : d.id;
|
|
152
|
+
const drop = m.prefabs.get(did);
|
|
153
|
+
if (!drop || prefabKind(m, did, drop) !== "pickup") {
|
|
154
|
+
issues.push(`prefab "${id}" drops "${did}", which is an enemy; drops must be pickups`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const special = specialIds.indexOf(did);
|
|
158
|
+
if (special >= 0)
|
|
159
|
+
specialDrops.push({ pickup: special, chance: typeof d === "string" ? 1 : d.chance });
|
|
160
|
+
else if (typeof d === "string")
|
|
161
|
+
xp += drop.value ?? 1;
|
|
162
|
+
else
|
|
163
|
+
issues.push(`prefab "${id}" drops "${did}" with a chance; chances are for special pickups (pickup: { effect }); XP gems always drop`);
|
|
164
|
+
}
|
|
165
|
+
enemyPrefabs.push(id);
|
|
166
|
+
enemyTypes.push({
|
|
167
|
+
health: p.health,
|
|
168
|
+
speed: p.speed,
|
|
169
|
+
radius: p.radius ?? DEFAULT_RADIUS,
|
|
170
|
+
damage: p.damage ?? DEFAULT_DAMAGE,
|
|
171
|
+
xp,
|
|
172
|
+
...(p.boss ? { boss: true } : {}),
|
|
173
|
+
mass: p.mass ?? (p.boss ? 20 : 1),
|
|
174
|
+
ai: p.prop ? "idle" : enemyAi(p.ai),
|
|
175
|
+
...(specialDrops.length ? { drops: specialDrops } : {}),
|
|
176
|
+
...(p.prop ? { prop: true } : {}),
|
|
177
|
+
...(p.attack ? { attack: compileAttack(id, p, tickRate, enemyRef) } : {}),
|
|
178
|
+
...(p.split ? splitOf(id, p.split, enemyRef, issues) : {}),
|
|
179
|
+
...(typeof p.boss === "object"
|
|
180
|
+
? {
|
|
181
|
+
pattern: {
|
|
182
|
+
attacks: p.boss.attacks,
|
|
183
|
+
enrageAt: p.boss.enrageAt,
|
|
184
|
+
...(p.boss.summon !== undefined ? { summon: enemyRef(id, "boss.summon", p.boss.summon) ?? 0 } : {}),
|
|
185
|
+
...(p.boss.rank === "mini" ? { mini: true } : {}),
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (enemyTypes.length > 255)
|
|
192
|
+
issues.push("at most 255 enemy prefabs");
|
|
193
|
+
const weaponIds = [...m.weapons.keys()];
|
|
194
|
+
const upgradeIds = [...m.upgrades.keys()];
|
|
195
|
+
// Evolution targets are never offered on their own.
|
|
196
|
+
const evolvedIds = new Set();
|
|
197
|
+
for (const [id, w] of m.weapons) {
|
|
198
|
+
if (!w.evolve)
|
|
199
|
+
continue;
|
|
200
|
+
if (w.evolve.into === id)
|
|
201
|
+
issues.push(`weapon "${id}" evolves into itself: evolve into a separate, stronger weapon`);
|
|
202
|
+
else if (!m.weapons.has(w.evolve.into))
|
|
203
|
+
issues.push(`weapon "${id}" evolves into "${w.evolve.into}", which is not a declared weapon`);
|
|
204
|
+
if (!m.upgrades.has(w.evolve.requires))
|
|
205
|
+
issues.push(`weapon "${id}" evolves with "${w.evolve.requires}", which is not a declared upgrade`);
|
|
206
|
+
evolvedIds.add(w.evolve.into);
|
|
207
|
+
}
|
|
208
|
+
const weapons = [...m.weapons.entries()].map(([id, w]) => {
|
|
209
|
+
if (w.levels && w.maxLevel !== undefined && w.maxLevel !== w.levels.length + 1) {
|
|
210
|
+
issues.push(`weapon "${id}" declares ${w.levels.length} levels and maxLevel ${w.maxLevel}: drop maxLevel (it is levels.length + 1)`);
|
|
211
|
+
}
|
|
212
|
+
const table = w.levels ? levelTable(w, tickRate) : undefined;
|
|
213
|
+
const base = baseLevel(w, tickRate);
|
|
214
|
+
return {
|
|
215
|
+
kind: w.kind,
|
|
216
|
+
damage: w.damage,
|
|
217
|
+
cooldownTicks: Math.min(255, Math.max(1, Math.round(base.cooldownTicks))),
|
|
218
|
+
range: w.range,
|
|
219
|
+
count: w.count,
|
|
220
|
+
speed: base.speed,
|
|
221
|
+
pierce: w.pierce,
|
|
222
|
+
knockback: w.knockback,
|
|
223
|
+
maxLevel: table ? table.length : (w.maxLevel ?? 8),
|
|
224
|
+
...(w.spread !== undefined || w.kind === "cone" ? { spread: base.spread } : {}),
|
|
225
|
+
...(w.blast !== undefined ? { blast: w.blast } : {}),
|
|
226
|
+
...(table ? { levels: table } : {}),
|
|
227
|
+
...(evolvedIds.has(id) ? { evolved: true } : {}),
|
|
228
|
+
...(w.evolve && m.weapons.has(w.evolve.into) && m.upgrades.has(w.evolve.requires)
|
|
229
|
+
? { evolve: { into: weaponIds.indexOf(w.evolve.into), requires: upgradeIds.indexOf(w.evolve.requires) } }
|
|
230
|
+
: {}),
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
const stats = [...m.upgrades.values()].map((u) => ({ stat: u.stat, step: u.step, maxLevel: u.maxLevel }));
|
|
234
|
+
const start = m.game.players.weapon;
|
|
235
|
+
const startWeapon = start === undefined ? -1 : weaponIds.indexOf(start);
|
|
236
|
+
// The co-op family on the survivor machinery: the survivor-like, the dungeon crawl and the party rounds.
|
|
237
|
+
const coopFamily = COOP_TEMPLATES.has(m.game.template);
|
|
238
|
+
if (coopFamily) {
|
|
239
|
+
if (enemyTypes.length === 0)
|
|
240
|
+
issues.push(`a ${m.game.template} game needs at least one enemy prefab`);
|
|
241
|
+
if (weapons.length === 0)
|
|
242
|
+
issues.push(`a ${m.game.template} game needs at least one weapon(...)`);
|
|
243
|
+
if (start === undefined && m.keepers.size === 0)
|
|
244
|
+
issues.push(`a ${m.game.template} game sets players.weapon (the weapon every run starts with) or declares keeper(...)s`);
|
|
245
|
+
for (const [id, w] of m.weapons) {
|
|
246
|
+
if (!SURVIVOR_KINDS.has(w.kind))
|
|
247
|
+
issues.push(`weapon "${id}" is a ${w.kind}; survivor weapons fire by themselves: nova, bolt or orbit (or cone, chain, aura, meteor, boomerang)`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const arenaMode = m.game.template === "arena";
|
|
251
|
+
const loadoutIds = m.game.players.loadout ?? [];
|
|
252
|
+
const loadout = loadoutIds.map((id) => weaponIds.indexOf(id)).filter((i) => i >= 0);
|
|
253
|
+
let dropHeal = 0;
|
|
254
|
+
if (arenaMode) {
|
|
255
|
+
const a = m.game.arena;
|
|
256
|
+
if (!a)
|
|
257
|
+
issues.push("an arena game sets arena: { halfSize, pillars, spawns } (the floor, cover and spawn points)");
|
|
258
|
+
if (loadoutIds.length === 0)
|
|
259
|
+
issues.push('an arena game sets players.loadout: the weapons every player carries, e.g. ["rifle", "scatter", "beam"]');
|
|
260
|
+
for (const [id, w] of m.weapons) {
|
|
261
|
+
if (!ARENA_KINDS.has(w.kind))
|
|
262
|
+
issues.push(`weapon "${id}" is a ${w.kind}; arena weapons fire where the player aims: blaster, beam or nova`);
|
|
263
|
+
}
|
|
264
|
+
if (a) {
|
|
265
|
+
const h = a.halfSize;
|
|
266
|
+
a.pillars.forEach(([x, z, r], i) => {
|
|
267
|
+
if (Math.abs(x) + r > h || Math.abs(z) + r > h)
|
|
268
|
+
issues.push(`arena.pillars[${i}] at [${x}, ${z}] does not fit on the floor (halfSize ${h})`);
|
|
269
|
+
});
|
|
270
|
+
a.spawns.forEach(([x, z], i) => {
|
|
271
|
+
if (Math.abs(x) > h - 1 || Math.abs(z) > h - 1)
|
|
272
|
+
issues.push(`arena.spawns[${i}] at [${x}, ${z}] is off the floor (keep 1 m inside halfSize ${h})`);
|
|
273
|
+
for (const [px, pz, r] of a.pillars) {
|
|
274
|
+
if (Math.hypot(x - px, z - pz) < r + 1)
|
|
275
|
+
issues.push(`arena.spawns[${i}] at [${x}, ${z}] is inside a pillar; move it 1 m clear`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const drop = m.game.players.drop;
|
|
280
|
+
if (drop !== undefined) {
|
|
281
|
+
const p = m.prefabs.get(drop);
|
|
282
|
+
if (!p || prefabKind(m, drop, p) !== "pickup")
|
|
283
|
+
issues.push(`players.drop "${drop}" must be a pickup prefab (kind: "pickup", value: the health it restores)`);
|
|
284
|
+
else
|
|
285
|
+
dropHeal = p.value ?? 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (m.game.arena || loadoutIds.length > 0 || m.game.round) {
|
|
289
|
+
issues.push("arena, round and players.loadout are for the arena template");
|
|
290
|
+
}
|
|
291
|
+
if (m.keepers.size > 0 && !coopFamily)
|
|
292
|
+
issues.push("keeper(...) is for the co-op templates (survivor, dungeon, party)");
|
|
293
|
+
const fieldDef = arenaMode ? undefined : m.game.field;
|
|
294
|
+
if (fieldDef?.walls || fieldDef?.floors) {
|
|
295
|
+
const h = fieldDef.halfSize;
|
|
296
|
+
const off = (b) => Math.abs(b[0] ?? 0) + (b[2] ?? 0) > h + 1e-6 || Math.abs(b[1] ?? 0) + (b[3] ?? 0) > h + 1e-6;
|
|
297
|
+
(fieldDef.walls ?? []).forEach((b, i) => {
|
|
298
|
+
if (off(b))
|
|
299
|
+
issues.push(`field.walls[${i}] at [${b[0]}, ${b[1]}] does not fit on the field (halfSize ${h})`);
|
|
300
|
+
});
|
|
301
|
+
(fieldDef.floors ?? []).forEach((b, i) => {
|
|
302
|
+
if (off(b))
|
|
303
|
+
issues.push(`field.floors[${i}] at [${b[0]}, ${b[1]}] does not fit on the field (halfSize ${h})`);
|
|
304
|
+
});
|
|
305
|
+
const floors = fieldDef.floors ?? [];
|
|
306
|
+
if (floors.length > 0 && !floors.some(([x, z, hx, hz]) => Math.abs(x) < hx - 1 && Math.abs(z) < hz - 1)) {
|
|
307
|
+
issues.push("players start at [0, 0]: one of field.floors must cover it (dungeon(...) puts the start room there)");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (issues.length > 0)
|
|
311
|
+
throw new GameDefinitionError("game module", issues);
|
|
312
|
+
// Gem tiers follow the drop value: a tier's prefab is the pickup whose value is closest.
|
|
313
|
+
const tierOf = (xp) => (xp >= 25 ? 2 : xp >= 5 ? 1 : 0);
|
|
314
|
+
const pickupPrefabs = [0, 1, 2].map((tier) => {
|
|
315
|
+
const exact = pickups.find((p) => tierOf(p.value) === tier);
|
|
316
|
+
return exact?.id ?? pickups[0]?.id ?? "";
|
|
317
|
+
});
|
|
318
|
+
const a = arenaMode ? m.game.arena : undefined;
|
|
319
|
+
const field = arenaMode ? undefined : m.game.field;
|
|
320
|
+
const view = viewRadii(opts.views ?? viewsFor(m.game.template));
|
|
321
|
+
const pickupDefs = specialIds.map((id) => {
|
|
322
|
+
const p = m.prefabs.get(id)?.pickup;
|
|
323
|
+
return { effect: p?.effect ?? "heal", amount: p?.amount ?? (p?.effect === "chest" ? 2 : p?.effect === "heal" ? 0.3 : 1), ...(p?.evolve ? { evolve: true } : {}) };
|
|
324
|
+
});
|
|
325
|
+
// Elites drop the plain chest; bosses the boss chest (evolve) when the game declares one.
|
|
326
|
+
const lootOf = (effect) => pickupDefs.findIndex((d) => d.effect === effect && !("evolve" in d && d.evolve));
|
|
327
|
+
const plainChest = lootOf("chest");
|
|
328
|
+
const bossChest = pickupDefs.findIndex((d) => d.effect === "chest" && "evolve" in d && d.evolve);
|
|
329
|
+
const pl = m.game.players;
|
|
330
|
+
const share = typeof pl.share === "string" ? { mode: pl.share, fraction: SURVIVOR_DEFAULTS.share.fraction, radius: SURVIVOR_DEFAULTS.share.radius } : (pl.share ?? SURVIVOR_DEFAULTS.share);
|
|
331
|
+
const buff = pl.reviveBuff ?? SURVIVOR_DEFAULTS.reviveBuff;
|
|
332
|
+
const down = pl.downed ?? SURVIVOR_DEFAULTS.downed;
|
|
333
|
+
// Keepers: index order is declaration order; each starts with its weapon and its passive at level 1.
|
|
334
|
+
const characters = [...m.keepers.values()].map((k) => ({ weapon: weaponIds.indexOf(k.weapon), stat: k.passive ? upgradeIds.indexOf(k.passive) : -1 }));
|
|
335
|
+
const content = {
|
|
336
|
+
enemyTypes,
|
|
337
|
+
weapons,
|
|
338
|
+
stats,
|
|
339
|
+
progression: {
|
|
340
|
+
startWeapon: arenaMode ? (loadout[0] ?? startWeapon) : startWeapon,
|
|
341
|
+
...(arenaMode ? { loadout } : {}),
|
|
342
|
+
...(pl.xp ? { xpBase: pl.xp.base, xpGrowth: pl.xp.growth, xpExponent: pl.xp.exponent ?? 1 } : {}),
|
|
343
|
+
...(arenaMode
|
|
344
|
+
? {}
|
|
345
|
+
: {
|
|
346
|
+
magnet: pl.magnet ?? SURVIVOR_DEFAULTS.magnet,
|
|
347
|
+
rerolls: pl.rerolls ?? 1,
|
|
348
|
+
xpShare: { mode: share.mode, fraction: share.fraction, radius: share.radius },
|
|
349
|
+
}),
|
|
350
|
+
...(pl.crit ? { critChance: pl.crit.chance, critMultiplier: pl.crit.multiplier } : {}),
|
|
351
|
+
},
|
|
352
|
+
...(characters.length > 0 && !arenaMode ? { characters } : {}),
|
|
353
|
+
view: { radii: view },
|
|
354
|
+
...(pickupDefs.length ? { pickups: pickupDefs, loot: { chest: plainChest >= 0 ? plainChest : bossChest, heal: lootOf("heal"), vacuum: lootOf("vacuum"), ...(bossChest >= 0 ? { bossChest } : {}) } } : {}),
|
|
355
|
+
...(arenaMode
|
|
356
|
+
? {}
|
|
357
|
+
: {
|
|
358
|
+
coop: {
|
|
359
|
+
hitIframeTicks: Math.round((pl.hitIframes ?? SURVIVOR_DEFAULTS.hitIframes) * tickRate),
|
|
360
|
+
pauseSolo: true,
|
|
361
|
+
choicePulseRadius: SURVIVOR_DEFAULTS.choicePulseRadius,
|
|
362
|
+
choicePulseIframeTicks: Math.round(SURVIVOR_DEFAULTS.choicePulseIframes * tickRate),
|
|
363
|
+
reviveBuffTicks: Math.min(4095, Math.round(buff.seconds * tickRate)),
|
|
364
|
+
reviveBuffMight: buff.damage,
|
|
365
|
+
reviveBuffSpeed: buff.speed,
|
|
366
|
+
downedTicks: Math.round(down.seconds * tickRate),
|
|
367
|
+
crawlSpeed: down.crawl,
|
|
368
|
+
selfRevives: down.selfRevives,
|
|
369
|
+
respawnTicks: Math.min(4095, Math.round(down.respawn * tickRate)),
|
|
370
|
+
risePulseRadius: SURVIVOR_DEFAULTS.risePulseRadius,
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
...(a
|
|
374
|
+
? {
|
|
375
|
+
arena: {
|
|
376
|
+
enabled: true,
|
|
377
|
+
spawns: a.spawns.map(([x, z]) => [x, z]),
|
|
378
|
+
respawnTicks: Math.max(1, Math.round((m.game.players.respawn ?? ARENA_DEFAULTS.respawnSeconds) * tickRate)),
|
|
379
|
+
spawnIframeTicks: Math.round(ARENA_DEFAULTS.spawnProtectSeconds * tickRate),
|
|
380
|
+
dropHeal,
|
|
381
|
+
pickupTicks: Math.round(ARENA_DEFAULTS.pickupSeconds * tickRate),
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
: {}),
|
|
385
|
+
};
|
|
386
|
+
return {
|
|
387
|
+
manifest: m,
|
|
388
|
+
sim: {
|
|
389
|
+
tickRate,
|
|
390
|
+
maxEntities: opts.maxEntities ?? (arenaMode ? 2048 : 4096),
|
|
391
|
+
enemies: { target: 0 },
|
|
392
|
+
...(a
|
|
393
|
+
? {
|
|
394
|
+
arena: {
|
|
395
|
+
halfExtent: a.halfSize,
|
|
396
|
+
wallHeight: 3,
|
|
397
|
+
pillars: a.pillars.map(([x, z, radius]) => ({ x, z, radius, height: ARENA_DEFAULTS.pillarHeight })),
|
|
398
|
+
spawn: [0, 1, 0],
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
: field
|
|
402
|
+
? {
|
|
403
|
+
arena: {
|
|
404
|
+
halfExtent: field.halfSize,
|
|
405
|
+
wallHeight: 3,
|
|
406
|
+
pillars: field.pillars.map(([x, z, radius]) => ({ x, z, radius, height: radius >= 2.5 ? 5 : 4 })),
|
|
407
|
+
...(field.walls ? { walls: field.walls.map(([x, z, hx, hz]) => ({ x, z, hx, hz })) } : {}),
|
|
408
|
+
...(field.floors ? { floors: field.floors.map(([x, z, hx, hz]) => ({ x, z, hx, hz })) } : {}),
|
|
409
|
+
spawn: [0, 1, 0],
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
: {}),
|
|
413
|
+
content,
|
|
414
|
+
},
|
|
415
|
+
mode: arenaMode ? "arena" : "survivor",
|
|
416
|
+
loadout,
|
|
417
|
+
bots: arenaMode ? (m.game.players.bots ?? 0) : 0,
|
|
418
|
+
round: arenaMode ? { seconds: m.game.round?.seconds ?? ARENA_DEFAULTS.roundSeconds, frags: m.game.round?.frags ?? null } : null,
|
|
419
|
+
view,
|
|
420
|
+
enemyPrefabs,
|
|
421
|
+
enemyType: new Map(enemyPrefabs.map((id, i) => [id, i])),
|
|
422
|
+
pickupPrefabs,
|
|
423
|
+
specialPickups: specialIds,
|
|
424
|
+
weaponIds,
|
|
425
|
+
upgradeIds,
|
|
426
|
+
tickRate,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/** The platformer template: no enemies or weapons, a race over declared levels (platformer.ts). */
|
|
430
|
+
function compilePlatformerGame(m, issues) {
|
|
431
|
+
const tickRate = m.game.tickRate ?? PLATFORMER_DEFAULTS.tickRate;
|
|
432
|
+
const platformer = compilePlatformer(m, tickRate, issues);
|
|
433
|
+
if (issues.length > 0)
|
|
434
|
+
throw new GameDefinitionError("game module", issues);
|
|
435
|
+
return {
|
|
436
|
+
manifest: m,
|
|
437
|
+
sim: { tickRate, enemies: { target: 0 } },
|
|
438
|
+
mode: "platformer",
|
|
439
|
+
platformer,
|
|
440
|
+
loadout: [],
|
|
441
|
+
bots: m.game.players.bots ?? PLATFORMER_DEFAULTS.bots,
|
|
442
|
+
round: { seconds: m.game.round?.seconds ?? PLATFORMER_DEFAULTS.roundSeconds, frags: null },
|
|
443
|
+
view: [],
|
|
444
|
+
enemyPrefabs: [],
|
|
445
|
+
enemyType: new Map(),
|
|
446
|
+
pickupPrefabs: [],
|
|
447
|
+
specialPickups: [],
|
|
448
|
+
weaponIds: [],
|
|
449
|
+
upgradeIds: [...m.upgrades.keys()],
|
|
450
|
+
tickRate,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/** The racer template: karts on circuits, a race a round (racer.ts). */
|
|
454
|
+
function compileRacerGame(m, issues) {
|
|
455
|
+
const tickRate = m.game.tickRate ?? RACER_DEFAULTS.tickRate;
|
|
456
|
+
const racer = compileRacer(m, issues);
|
|
457
|
+
if (issues.length > 0)
|
|
458
|
+
throw new GameDefinitionError("game module", issues);
|
|
459
|
+
return {
|
|
460
|
+
manifest: m,
|
|
461
|
+
sim: { tickRate, enemies: { target: 0 } },
|
|
462
|
+
mode: "racer",
|
|
463
|
+
racer,
|
|
464
|
+
loadout: [],
|
|
465
|
+
bots: m.game.players.bots ?? RACER_DEFAULTS.bots,
|
|
466
|
+
round: { seconds: m.game.round?.seconds ?? RACER_DEFAULTS.roundSeconds, frags: null },
|
|
467
|
+
view: [],
|
|
468
|
+
enemyPrefabs: [],
|
|
469
|
+
enemyType: new Map(),
|
|
470
|
+
pickupPrefabs: [],
|
|
471
|
+
specialPickups: [],
|
|
472
|
+
weaponIds: [],
|
|
473
|
+
upgradeIds: [...m.upgrades.keys()],
|
|
474
|
+
tickRate,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SimWorld } from "@netforge/engine-sim";
|
|
2
|
+
import type { Curve, DirectorDef } from "../api/schemas.js";
|
|
3
|
+
/** Value of a curve at `t` seconds: linear between keyframes, held outside them. */
|
|
4
|
+
export declare function curveAt(c: Curve, t: number): number;
|
|
5
|
+
export interface DirectorHooks {
|
|
6
|
+
/** Engine enemy type index of a prefab (undefined for pickups). */
|
|
7
|
+
readonly typeOf: (prefab: string) => number | undefined;
|
|
8
|
+
/** Prefab radius, m. */
|
|
9
|
+
readonly radiusOf: (prefab: string) => number;
|
|
10
|
+
/** Called for each spawn (the runner tracks bosses). */
|
|
11
|
+
readonly spawned?: (prefab: string, netId: number) => void;
|
|
12
|
+
/** True while a boss is alive. */
|
|
13
|
+
readonly bossAlive: () => boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class Director {
|
|
16
|
+
private readonly def;
|
|
17
|
+
private readonly world;
|
|
18
|
+
private readonly hooks;
|
|
19
|
+
private budget;
|
|
20
|
+
/** Bodies the cap held back, owed as toughness. */
|
|
21
|
+
private pressure;
|
|
22
|
+
/** Spawn-rate multiplier set by systems (events), and until when. */
|
|
23
|
+
private boost;
|
|
24
|
+
private boostUntil;
|
|
25
|
+
private pausedUntil;
|
|
26
|
+
/** Run tick of the next elite (-1: none planned). */
|
|
27
|
+
private nextElite;
|
|
28
|
+
constructor(def: DirectorDef, world: SimWorld, hooks: DirectorHooks);
|
|
29
|
+
/** Systems: stop spawning for `ticks` from `now` (events, set pieces). */
|
|
30
|
+
pause(now: number, ticks: number): void;
|
|
31
|
+
/** Systems: multiply the spawn rate by `k` for `ticks` from `now`. */
|
|
32
|
+
surge(now: number, k: number, ticks: number): void;
|
|
33
|
+
/** Players present (at least 1), for the per-player scaling. */
|
|
34
|
+
private players;
|
|
35
|
+
/** The living-enemy target at run tick `tick` (scaled by players). */
|
|
36
|
+
aliveTarget(tick: number): number;
|
|
37
|
+
/** One run tick. */
|
|
38
|
+
tick(tick: number): void;
|
|
39
|
+
private pick;
|
|
40
|
+
/** Spawn up to `count` of a prefab from one heading off one player's screen. */
|
|
41
|
+
private spawnGroup;
|
|
42
|
+
}
|