@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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card battler declarations (game({ template: "cards" })). The template
|
|
3
|
+
* already runs the duel (heroes, mana, draws, a board of units, spells,
|
|
4
|
+
* guards, the turn timer), the gauntlet of duels with a draft between
|
|
5
|
+
* them, the server-side shuffle, hidden hands, the AI opponents and the
|
|
6
|
+
* referee. A game declares its table rules and its cards.
|
|
7
|
+
*
|
|
8
|
+
* cardTable({ health: 20, duels: 4, starter: ["blot_imp", "blot_imp", ...], ai: [{ health: 14, smart: 0.3 }] });
|
|
9
|
+
* card("ink_frog", { model: "style.lowpoly.frog", kind: "unit", cost: 2, attack: 2, health: 3 });
|
|
10
|
+
* card("splash", { model: "style.lowpoly.crystal", kind: "spell", cost: 1, effect: { kind: "damage", amount: 2, target: "enemy" } });
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { contentOf, declareContent } from "./registry.js";
|
|
14
|
+
import { assetIdSchema, GameDefinitionError, idSchema, parseOrThrow } from "./schemas.js";
|
|
15
|
+
export const EFFECT_KINDS = ["damage", "heal", "draw", "buff"];
|
|
16
|
+
/**
|
|
17
|
+
* Who an effect hits. "enemy": the player picks an enemy unit or the enemy
|
|
18
|
+
* hero; "enemy_hero"; "all_enemies": every enemy unit; "own_hero";
|
|
19
|
+
* "own_unit": the player picks one of their units; "all_own": every unit
|
|
20
|
+
* they have. draw ignores the target.
|
|
21
|
+
*/
|
|
22
|
+
export const EFFECT_TARGETS = ["enemy", "enemy_hero", "all_enemies", "own_hero", "own_unit", "all_own"];
|
|
23
|
+
export const cardEffectSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
kind: z.enum(EFFECT_KINDS, { error: `effect kinds are: ${EFFECT_KINDS.join(", ")}` }),
|
|
26
|
+
/** Damage, healing, cards drawn, or attack and health added (buff). */
|
|
27
|
+
amount: z.number().int().min(1).max(30),
|
|
28
|
+
target: z.enum(EFFECT_TARGETS, { error: `effect targets are: ${EFFECT_TARGETS.join(", ")}` }).default("enemy"),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
/**
|
|
32
|
+
* A card. A "unit" stands on the board with attack and health; `guard`
|
|
33
|
+
* units must be attacked first; `rush` units may attack the turn they are
|
|
34
|
+
* played. A unit's `effect` happens when it is played. A "spell" is only
|
|
35
|
+
* its effect. cost is mana (0 to 10).
|
|
36
|
+
*/
|
|
37
|
+
export const cardSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
model: assetIdSchema,
|
|
40
|
+
kind: z.enum(["unit", "spell"]),
|
|
41
|
+
cost: z.number().int().min(0).max(10),
|
|
42
|
+
attack: z.number().int().min(0).max(30).default(0),
|
|
43
|
+
health: z.number().int().min(0).max(60).default(0),
|
|
44
|
+
guard: z.boolean().default(false),
|
|
45
|
+
rush: z.boolean().default(false),
|
|
46
|
+
effect: cardEffectSchema.optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict()
|
|
49
|
+
.refine((c) => c.kind === "spell" || c.health >= 1, "a unit needs health of at least 1")
|
|
50
|
+
.refine((c) => c.kind === "unit" || c.effect !== undefined, "a spell needs an effect");
|
|
51
|
+
export const cardTableSchema = z
|
|
52
|
+
.object({
|
|
53
|
+
/** Each hero's health at the start of a duel. */
|
|
54
|
+
health: z.number().int().min(5).max(100).default(20),
|
|
55
|
+
/** Cards in hand at the start of a duel (the second player draws one more). */
|
|
56
|
+
startHand: z.number().int().min(1).max(7).default(3),
|
|
57
|
+
handMax: z.number().int().min(3).max(10).default(7),
|
|
58
|
+
boardMax: z.number().int().min(1).max(7).default(5),
|
|
59
|
+
/** Mana rises by one a turn up to this. */
|
|
60
|
+
manaMax: z.number().int().min(1).max(10).default(10),
|
|
61
|
+
/** Seconds a turn lasts before it ends by itself. */
|
|
62
|
+
turnSeconds: z.number().min(10).max(120).default(30),
|
|
63
|
+
/** Seconds to pick a card between duels (the best card is picked for a player who does not). */
|
|
64
|
+
draftSeconds: z.number().min(5).max(60).default(15),
|
|
65
|
+
/** Duels in a run (the gauntlet). A draft follows every duel but the last. */
|
|
66
|
+
duels: z.number().int().min(1).max(12).default(4),
|
|
67
|
+
/** Each player's starting deck: declared card ids, 8 to 30 cards. */
|
|
68
|
+
starter: z.array(idSchema).min(8).max(30),
|
|
69
|
+
/** Cards the draft offers (default: every declared card). */
|
|
70
|
+
pool: z.array(idSchema).max(60).optional(),
|
|
71
|
+
/**
|
|
72
|
+
* The AI opponents of the gauntlet, in order: duel n against the AI uses
|
|
73
|
+
* tier n (the last tier repeats). health is the AI hero's health; smart
|
|
74
|
+
* (0 to 1) how often it plays its best move.
|
|
75
|
+
*/
|
|
76
|
+
ai: z
|
|
77
|
+
.array(z.object({ health: z.number().int().min(5).max(100), smart: z.number().min(0).max(1) }).strict())
|
|
78
|
+
.min(1)
|
|
79
|
+
.max(12),
|
|
80
|
+
/** Turns a duel may last before it ends in a draw. */
|
|
81
|
+
turnCap: z.number().int().min(10).max(200).default(40),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
/** The table: hero health, hand and board sizes, the gauntlet, the starter deck and the AI tiers (one per game). */
|
|
85
|
+
export function cardTable(def) {
|
|
86
|
+
const parsed = parseOrThrow(cardTableSchema, def, "cardTable()");
|
|
87
|
+
declareContent("cardTable", "table", parsed);
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
/** A card (at most 40). */
|
|
91
|
+
export function card(id, def) {
|
|
92
|
+
parseOrThrow(idSchema, id, "card id");
|
|
93
|
+
const parsed = parseOrThrow(cardSchema, def, `card("${id}")`);
|
|
94
|
+
declareContent("card", id, parsed);
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
/** Store upgrade stats a card battler reads: hero health, unit attack, starting mana, starting hand. */
|
|
98
|
+
export const CARD_STATS = ["maxHealth", "might", "haste", "amount"];
|
|
99
|
+
/** The card battler content of a manifest, cross-checked (one line per issue). */
|
|
100
|
+
export function cardsContent(m) {
|
|
101
|
+
const issues = [];
|
|
102
|
+
const table = contentOf(m, "cardTable")[0]?.[1];
|
|
103
|
+
const cards = contentOf(m, "card");
|
|
104
|
+
if (!table)
|
|
105
|
+
issues.push("declare the table: cardTable({ health, duels, starter: [...], ai: [{ health, smart }] })");
|
|
106
|
+
if (cards.length < 4)
|
|
107
|
+
issues.push('declare at least 4 cards: card("ink_frog", { model, kind: "unit", cost, attack, health })');
|
|
108
|
+
if (cards.length > 40)
|
|
109
|
+
issues.push("at most 40 cards per game");
|
|
110
|
+
if (m.weapons.size > 0)
|
|
111
|
+
issues.push("weapon(...) is for the survivor and arena templates; a card battler declares card(...)");
|
|
112
|
+
if (m.game.players.max > 2)
|
|
113
|
+
issues.push("players.max is at most 2: a duel is one against one");
|
|
114
|
+
for (const [id, u] of m.upgrades)
|
|
115
|
+
if (!CARD_STATS.includes(u.stat))
|
|
116
|
+
issues.push(`upgrade("${id}") raises ${u.stat}; a card battler reads ${CARD_STATS.join(", ")} (hero health, unit attack, starting mana, starting hand)`);
|
|
117
|
+
const ids = new Set(cards.map(([id]) => id));
|
|
118
|
+
if (table) {
|
|
119
|
+
for (const id of table.starter)
|
|
120
|
+
if (!ids.has(id))
|
|
121
|
+
issues.push(`cardTable starter has "${id}", which is not a declared card`);
|
|
122
|
+
for (const id of table.pool ?? [])
|
|
123
|
+
if (!ids.has(id))
|
|
124
|
+
issues.push(`cardTable pool has "${id}", which is not a declared card`);
|
|
125
|
+
if (!table.starter.some((id) => cards.find(([c]) => c === id)?.[1].kind === "unit"))
|
|
126
|
+
issues.push("the starter deck needs at least one unit card");
|
|
127
|
+
if (!table.starter.some((id) => (cards.find(([c]) => c === id)?.[1].cost ?? 99) <= 2))
|
|
128
|
+
issues.push("the starter deck needs cards costing 2 or less, or the first turns have nothing to play");
|
|
129
|
+
}
|
|
130
|
+
if (issues.length > 0 || !table)
|
|
131
|
+
throw new GameDefinitionError("card battler", issues);
|
|
132
|
+
return { table, cards };
|
|
133
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monster collector declarations (game({ template: "collector" })). The
|
|
3
|
+
* template already runs the walkable marsh, encounters in tall grass,
|
|
4
|
+
* turn-based battles, catching, a team of three, XP and levels, the shared
|
|
5
|
+
* warden fight, the dusk clock and the referee; a game declares its marsh,
|
|
6
|
+
* its creatures and their moves.
|
|
7
|
+
*
|
|
8
|
+
* marsh({ size: [28, 18], camp: [2, 9], starters: ["mossling", "cindertail", "puddlewump"],
|
|
9
|
+
* warden: { creature: "fenwarden", at: [25, 9], level: 12, hp: 900 },
|
|
10
|
+
* grass: [{ at: [6, 3, 5, 4], rate: 0.2, pool: [{ creature: "sporecap", weight: 2, level: [2, 4] }] }] });
|
|
11
|
+
* creature("mossling", { model: "style.lowpoly.frog", element: "root", hp: 46, atk: 48, def: 50, spd: 44, moves: ["leaf_lash", "sap_drink"] });
|
|
12
|
+
* move("leaf_lash", { element: "root", power: 40 });
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import type { GameManifest } from "./registry.js";
|
|
16
|
+
/**
|
|
17
|
+
* The four elements, a cycle: each one is strong against the next and weak
|
|
18
|
+
* against the one before (root soaks tide, tide shorts spark, spark cracks
|
|
19
|
+
* stone, stone crushes root). Strong hits deal 1.5x, weak ones 0.67x.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ELEMENTS: readonly ["root", "tide", "spark", "stone"];
|
|
22
|
+
export type Element = (typeof ELEMENTS)[number];
|
|
23
|
+
export declare const marshSchema: z.ZodObject<{
|
|
24
|
+
size: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
25
|
+
camp: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
26
|
+
starters: z.ZodArray<z.ZodString>;
|
|
27
|
+
starterLevel: z.ZodDefault<z.ZodNumber>;
|
|
28
|
+
blocked: z.ZodDefault<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>>;
|
|
29
|
+
water: z.ZodDefault<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>>;
|
|
30
|
+
grass: z.ZodArray<z.ZodObject<{
|
|
31
|
+
at: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
|
|
32
|
+
rate: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
pool: z.ZodArray<z.ZodObject<{
|
|
34
|
+
creature: z.ZodString;
|
|
35
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
level: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
37
|
+
}, z.core.$strict>>;
|
|
38
|
+
}, z.core.$strict>>;
|
|
39
|
+
warden: z.ZodObject<{
|
|
40
|
+
creature: z.ZodString;
|
|
41
|
+
at: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
42
|
+
level: z.ZodNumber;
|
|
43
|
+
hp: z.ZodNumber;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
dusk: z.ZodDefault<z.ZodNumber>;
|
|
46
|
+
snares: z.ZodDefault<z.ZodNumber>;
|
|
47
|
+
walk: z.ZodDefault<z.ZodNumber>;
|
|
48
|
+
}, z.core.$strict>;
|
|
49
|
+
export declare const MOVE_EFFECTS: readonly ["none", "drain", "heal", "guard"];
|
|
50
|
+
/**
|
|
51
|
+
* A move. power 0 to 200 (40 is a plain hit), accuracy 0.3 to 1. effect:
|
|
52
|
+
* "drain" heals the user half the damage dealt, "heal" (power 0 allowed)
|
|
53
|
+
* heals the user `amount` of its max health, "guard" halves the next hit
|
|
54
|
+
* it takes.
|
|
55
|
+
*/
|
|
56
|
+
export declare const moveSchema: z.ZodObject<{
|
|
57
|
+
element: z.ZodEnum<{
|
|
58
|
+
root: "root";
|
|
59
|
+
tide: "tide";
|
|
60
|
+
spark: "spark";
|
|
61
|
+
stone: "stone";
|
|
62
|
+
}>;
|
|
63
|
+
power: z.ZodNumber;
|
|
64
|
+
accuracy: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
effect: z.ZodDefault<z.ZodEnum<{
|
|
66
|
+
none: "none";
|
|
67
|
+
heal: "heal";
|
|
68
|
+
drain: "drain";
|
|
69
|
+
guard: "guard";
|
|
70
|
+
}>>;
|
|
71
|
+
amount: z.ZodDefault<z.ZodNumber>;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
/**
|
|
74
|
+
* A creature. Base stats at level 1 (each grows 12% a level): hp, atk,
|
|
75
|
+
* def, spd (who strikes first). catchRate 0.05 to 1 scales every snare.
|
|
76
|
+
* boss: the warden kind (never caught, never met in grass).
|
|
77
|
+
*/
|
|
78
|
+
export declare const creatureSchema: z.ZodObject<{
|
|
79
|
+
model: z.ZodString;
|
|
80
|
+
element: z.ZodEnum<{
|
|
81
|
+
root: "root";
|
|
82
|
+
tide: "tide";
|
|
83
|
+
spark: "spark";
|
|
84
|
+
stone: "stone";
|
|
85
|
+
}>;
|
|
86
|
+
hp: z.ZodNumber;
|
|
87
|
+
atk: z.ZodNumber;
|
|
88
|
+
def: z.ZodNumber;
|
|
89
|
+
spd: z.ZodNumber;
|
|
90
|
+
moves: z.ZodArray<z.ZodString>;
|
|
91
|
+
catchRate: z.ZodDefault<z.ZodNumber>;
|
|
92
|
+
boss: z.ZodDefault<z.ZodBoolean>;
|
|
93
|
+
size: z.ZodDefault<z.ZodNumber>;
|
|
94
|
+
}, z.core.$strict>;
|
|
95
|
+
export type MarshInput = z.input<typeof marshSchema>;
|
|
96
|
+
export type MarshDef = z.output<typeof marshSchema>;
|
|
97
|
+
export type MoveInput = z.input<typeof moveSchema>;
|
|
98
|
+
export type MoveDef = z.output<typeof moveSchema>;
|
|
99
|
+
export type CreatureInput = z.input<typeof creatureSchema>;
|
|
100
|
+
export type CreatureDef = z.output<typeof creatureSchema>;
|
|
101
|
+
/** The marsh: its size, camp, starters, grass, blocked tiles and warden (one per game). */
|
|
102
|
+
export declare function marsh(def: MarshInput): MarshDef;
|
|
103
|
+
/** A creature players meet, fight and catch (at most 24). */
|
|
104
|
+
export declare function creature(id: string, def: CreatureInput): CreatureDef;
|
|
105
|
+
/** A move creatures use in battle (at most 48). */
|
|
106
|
+
export declare function move(id: string, def: MoveInput): MoveDef;
|
|
107
|
+
/** Store upgrade stats a collector game reads: team attack, team health, team defence, extra snares, healing between battles. */
|
|
108
|
+
export declare const COLLECTOR_STATS: readonly ["might", "maxHealth", "armor", "amount", "regen"];
|
|
109
|
+
/** The collector content of a manifest, cross-checked (one line per issue). */
|
|
110
|
+
export declare function collectorContent(m: GameManifest): {
|
|
111
|
+
marsh: MarshDef;
|
|
112
|
+
creatures: [string, CreatureDef][];
|
|
113
|
+
moves: [string, MoveDef][];
|
|
114
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monster collector declarations (game({ template: "collector" })). The
|
|
3
|
+
* template already runs the walkable marsh, encounters in tall grass,
|
|
4
|
+
* turn-based battles, catching, a team of three, XP and levels, the shared
|
|
5
|
+
* warden fight, the dusk clock and the referee; a game declares its marsh,
|
|
6
|
+
* its creatures and their moves.
|
|
7
|
+
*
|
|
8
|
+
* marsh({ size: [28, 18], camp: [2, 9], starters: ["mossling", "cindertail", "puddlewump"],
|
|
9
|
+
* warden: { creature: "fenwarden", at: [25, 9], level: 12, hp: 900 },
|
|
10
|
+
* grass: [{ at: [6, 3, 5, 4], rate: 0.2, pool: [{ creature: "sporecap", weight: 2, level: [2, 4] }] }] });
|
|
11
|
+
* creature("mossling", { model: "style.lowpoly.frog", element: "root", hp: 46, atk: 48, def: 50, spd: 44, moves: ["leaf_lash", "sap_drink"] });
|
|
12
|
+
* move("leaf_lash", { element: "root", power: 40 });
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { contentOf, declareContent } from "./registry.js";
|
|
16
|
+
import { assetIdSchema, GameDefinitionError, idSchema, parseOrThrow } from "./schemas.js";
|
|
17
|
+
/**
|
|
18
|
+
* The four elements, a cycle: each one is strong against the next and weak
|
|
19
|
+
* against the one before (root soaks tide, tide shorts spark, spark cracks
|
|
20
|
+
* stone, stone crushes root). Strong hits deal 1.5x, weak ones 0.67x.
|
|
21
|
+
*/
|
|
22
|
+
export const ELEMENTS = ["root", "tide", "spark", "stone"];
|
|
23
|
+
const rect = z.tuple([z.number().int().min(0).max(63), z.number().int().min(0).max(63), z.number().int().min(1).max(64), z.number().int().min(1).max(64)]);
|
|
24
|
+
const cell = z.tuple([z.number().int().min(0).max(63), z.number().int().min(0).max(63)]);
|
|
25
|
+
export const marshSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
/** Tiles across and down. Each tile is 2 m. */
|
|
28
|
+
size: z.tuple([z.number().int().min(10).max(64), z.number().int().min(8).max(64)]),
|
|
29
|
+
/** The camp tile: everyone starts here, and a team that faints comes back here. Standing on it heals. */
|
|
30
|
+
camp: cell,
|
|
31
|
+
/** Creatures a new player picks one of to start (declared creatures, 1 to 4). */
|
|
32
|
+
starters: z.array(idSchema).min(1).max(4),
|
|
33
|
+
/** Level the starter begins at. */
|
|
34
|
+
starterLevel: z.number().int().min(1).max(30).default(3),
|
|
35
|
+
/** Rocks, water and thickets nobody walks through: [x, y, width, height] in tiles. */
|
|
36
|
+
blocked: z.array(rect).max(64).default([]),
|
|
37
|
+
/** Water among the blocked rects is drawn as water: [x, y, width, height]. */
|
|
38
|
+
water: z.array(rect).max(32).default([]),
|
|
39
|
+
/** Tall grass: where wild creatures are met. `rate` is the chance a step into a new grass tile starts a battle. */
|
|
40
|
+
grass: z
|
|
41
|
+
.array(z
|
|
42
|
+
.object({
|
|
43
|
+
at: rect,
|
|
44
|
+
rate: z.number().min(0.01).max(0.6).default(0.15),
|
|
45
|
+
pool: z
|
|
46
|
+
.array(z.object({ creature: idSchema, weight: z.number().min(0.01).max(100).default(1), level: z.tuple([z.number().int().min(1).max(50), z.number().int().min(1).max(50)]) }).strict())
|
|
47
|
+
.min(1)
|
|
48
|
+
.max(12),
|
|
49
|
+
})
|
|
50
|
+
.strict())
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(24),
|
|
53
|
+
/**
|
|
54
|
+
* The warden: one creature everyone fights together. Its health is one
|
|
55
|
+
* pool for the whole marsh (more players, more health). Beating it wins
|
|
56
|
+
* the run for everyone who hurt it.
|
|
57
|
+
*/
|
|
58
|
+
warden: z
|
|
59
|
+
.object({
|
|
60
|
+
creature: idSchema,
|
|
61
|
+
at: cell,
|
|
62
|
+
level: z.number().int().min(1).max(50),
|
|
63
|
+
/** Health of the shared pool for one player; +50% a player past the first. */
|
|
64
|
+
hp: z.number().int().min(10).max(1_000_000),
|
|
65
|
+
})
|
|
66
|
+
.strict(),
|
|
67
|
+
/** Seconds until dusk: the run ends then, lost unless the warden fell. */
|
|
68
|
+
dusk: z.number().min(120).max(3600).default(600),
|
|
69
|
+
/** Snares each player starts a run with. */
|
|
70
|
+
snares: z.number().int().min(0).max(50).default(6),
|
|
71
|
+
/** Walking speed, metres a second. */
|
|
72
|
+
walk: z.number().min(1).max(12).default(4.5),
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
export const MOVE_EFFECTS = ["none", "drain", "heal", "guard"];
|
|
76
|
+
/**
|
|
77
|
+
* A move. power 0 to 200 (40 is a plain hit), accuracy 0.3 to 1. effect:
|
|
78
|
+
* "drain" heals the user half the damage dealt, "heal" (power 0 allowed)
|
|
79
|
+
* heals the user `amount` of its max health, "guard" halves the next hit
|
|
80
|
+
* it takes.
|
|
81
|
+
*/
|
|
82
|
+
export const moveSchema = z
|
|
83
|
+
.object({
|
|
84
|
+
element: z.enum(ELEMENTS, { error: `elements are: ${ELEMENTS.join(", ")}` }),
|
|
85
|
+
power: z.number().int().min(0).max(200),
|
|
86
|
+
accuracy: z.number().min(0.3).max(1).default(1),
|
|
87
|
+
effect: z.enum(MOVE_EFFECTS).default("none"),
|
|
88
|
+
/** "heal": share of max health restored. */
|
|
89
|
+
amount: z.number().min(0).max(1).default(0.3),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
/**
|
|
93
|
+
* A creature. Base stats at level 1 (each grows 12% a level): hp, atk,
|
|
94
|
+
* def, spd (who strikes first). catchRate 0.05 to 1 scales every snare.
|
|
95
|
+
* boss: the warden kind (never caught, never met in grass).
|
|
96
|
+
*/
|
|
97
|
+
export const creatureSchema = z
|
|
98
|
+
.object({
|
|
99
|
+
model: assetIdSchema,
|
|
100
|
+
element: z.enum(ELEMENTS, { error: `elements are: ${ELEMENTS.join(", ")}` }),
|
|
101
|
+
hp: z.number().int().min(5).max(999),
|
|
102
|
+
atk: z.number().int().min(5).max(999),
|
|
103
|
+
def: z.number().int().min(5).max(999),
|
|
104
|
+
spd: z.number().int().min(5).max(999),
|
|
105
|
+
moves: z.array(idSchema).min(1).max(4),
|
|
106
|
+
catchRate: z.number().min(0.05).max(1).default(0.8),
|
|
107
|
+
boss: z.boolean().default(false),
|
|
108
|
+
/** Height of the model, metres. */
|
|
109
|
+
size: z.number().min(0.3).max(6).default(1),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
|
112
|
+
/** The marsh: its size, camp, starters, grass, blocked tiles and warden (one per game). */
|
|
113
|
+
export function marsh(def) {
|
|
114
|
+
const parsed = parseOrThrow(marshSchema, def, "marsh()");
|
|
115
|
+
declareContent("marsh", "marsh", parsed);
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
/** A creature players meet, fight and catch (at most 24). */
|
|
119
|
+
export function creature(id, def) {
|
|
120
|
+
parseOrThrow(idSchema, id, "creature id");
|
|
121
|
+
const parsed = parseOrThrow(creatureSchema, def, `creature("${id}")`);
|
|
122
|
+
declareContent("creature", id, parsed);
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
125
|
+
/** A move creatures use in battle (at most 48). */
|
|
126
|
+
export function move(id, def) {
|
|
127
|
+
parseOrThrow(idSchema, id, "move id");
|
|
128
|
+
const parsed = parseOrThrow(moveSchema, def, `move("${id}")`);
|
|
129
|
+
declareContent("move", id, parsed);
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
/** Store upgrade stats a collector game reads: team attack, team health, team defence, extra snares, healing between battles. */
|
|
133
|
+
export const COLLECTOR_STATS = ["might", "maxHealth", "armor", "amount", "regen"];
|
|
134
|
+
/** The collector content of a manifest, cross-checked (one line per issue). */
|
|
135
|
+
export function collectorContent(m) {
|
|
136
|
+
const issues = [];
|
|
137
|
+
const mm = contentOf(m, "marsh")[0]?.[1];
|
|
138
|
+
const creatures = contentOf(m, "creature");
|
|
139
|
+
const moves = contentOf(m, "move");
|
|
140
|
+
const cs = new Map(creatures);
|
|
141
|
+
const ms = new Map(moves);
|
|
142
|
+
if (!mm)
|
|
143
|
+
issues.push('declare the marsh: marsh({ size, camp, starters, grass, warden })');
|
|
144
|
+
if (creatures.length === 0)
|
|
145
|
+
issues.push('declare at least one creature: creature("mossling", { model, element, hp, atk, def, spd, moves })');
|
|
146
|
+
if (creatures.length > 24)
|
|
147
|
+
issues.push("at most 24 creatures per game");
|
|
148
|
+
if (moves.length > 48)
|
|
149
|
+
issues.push("at most 48 moves per game");
|
|
150
|
+
if (m.weapons.size > 0)
|
|
151
|
+
issues.push("weapon(...) is for the survivor and arena templates; a collector game declares creature(...) and move(...)");
|
|
152
|
+
for (const [id, u] of m.upgrades)
|
|
153
|
+
if (!COLLECTOR_STATS.includes(u.stat))
|
|
154
|
+
issues.push(`upgrade("${id}") raises ${u.stat}; a collector reads ${COLLECTOR_STATS.join(", ")} (team attack, team health, team defence, extra snares, healing between battles)`);
|
|
155
|
+
for (const [id, c] of creatures)
|
|
156
|
+
for (const mv of c.moves)
|
|
157
|
+
if (!ms.has(mv))
|
|
158
|
+
issues.push(`creature("${id}") knows "${mv}", which is not a declared move`);
|
|
159
|
+
if (mm) {
|
|
160
|
+
const [w, h] = mm.size;
|
|
161
|
+
const inside = (x, y) => x >= 0 && y >= 0 && x < w && y < h;
|
|
162
|
+
const blocked = (x, y) => mm.blocked.some(([bx, by, bw, bh]) => x >= bx && y >= by && x < bx + bw && y < by + bh);
|
|
163
|
+
if (!inside(mm.camp[0], mm.camp[1]))
|
|
164
|
+
issues.push(`marsh camp [${mm.camp[0]}, ${mm.camp[1]}] is off the ${w} x ${h} marsh`);
|
|
165
|
+
else if (blocked(mm.camp[0], mm.camp[1]))
|
|
166
|
+
issues.push("marsh camp stands on a blocked tile");
|
|
167
|
+
if (!inside(mm.warden.at[0], mm.warden.at[1]))
|
|
168
|
+
issues.push(`marsh warden at [${mm.warden.at[0]}, ${mm.warden.at[1]}] is off the marsh`);
|
|
169
|
+
else if (blocked(mm.warden.at[0], mm.warden.at[1]))
|
|
170
|
+
issues.push("the warden stands on a blocked tile");
|
|
171
|
+
const wc = cs.get(mm.warden.creature);
|
|
172
|
+
if (!wc)
|
|
173
|
+
issues.push(`marsh warden "${mm.warden.creature}" is not a declared creature`);
|
|
174
|
+
else if (!wc.boss)
|
|
175
|
+
issues.push(`the warden "${mm.warden.creature}" needs boss: true`);
|
|
176
|
+
for (const s of mm.starters) {
|
|
177
|
+
const c = cs.get(s);
|
|
178
|
+
if (!c)
|
|
179
|
+
issues.push(`marsh starter "${s}" is not a declared creature`);
|
|
180
|
+
else if (c.boss)
|
|
181
|
+
issues.push(`marsh starter "${s}" is a boss`);
|
|
182
|
+
}
|
|
183
|
+
for (const g of mm.grass) {
|
|
184
|
+
const [gx, gy, gw, gh] = g.at;
|
|
185
|
+
if (!inside(gx, gy) || !inside(gx + gw - 1, gy + gh - 1))
|
|
186
|
+
issues.push(`grass at [${g.at.join(", ")}] runs off the marsh`);
|
|
187
|
+
for (const p of g.pool) {
|
|
188
|
+
const c = cs.get(p.creature);
|
|
189
|
+
if (!c)
|
|
190
|
+
issues.push(`grass pool "${p.creature}" is not a declared creature`);
|
|
191
|
+
else if (c.boss)
|
|
192
|
+
issues.push(`grass pool "${p.creature}" is a boss; bosses are never met in grass`);
|
|
193
|
+
if (p.level[0] > p.level[1])
|
|
194
|
+
issues.push(`grass pool "${p.creature}": level [min, max] needs min <= max`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [bx, by, bw, bh] of mm.blocked)
|
|
198
|
+
if (!inside(bx, by) || !inside(bx + bw - 1, by + bh - 1))
|
|
199
|
+
issues.push(`blocked [${bx}, ${by}, ${bw}, ${bh}] runs off the marsh`);
|
|
200
|
+
}
|
|
201
|
+
if (issues.length > 0 || !mm)
|
|
202
|
+
throw new GameDefinitionError("collector", issues);
|
|
203
|
+
return { marsh: mm, creatures, moves };
|
|
204
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dungeon(...): a procedural dungeon floor from a seed
|
|
3
|
+
* ("dungeon layouts can be generated from a seed on both sides").
|
|
4
|
+
*
|
|
5
|
+
* Rooms sit on a grid of cells, grown from the start room as a random
|
|
6
|
+
* tree, with a few extra doors that make loops. Neighbouring rooms are
|
|
7
|
+
* joined by straight corridors. The result is the `field` a dungeon game
|
|
8
|
+
* passes to game({ field }) (walls and floors as boxes, the start room at
|
|
9
|
+
* [0, 0] where players spawn) and the rooms with their depth from the
|
|
10
|
+
* start, so a system can wake encounters room by room.
|
|
11
|
+
*
|
|
12
|
+
* Pure and deterministic: the same spec gives the same dungeon on the room
|
|
13
|
+
* server, in the client and in the playtest, with nothing downloaded.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
export declare const dungeonSchema: z.ZodObject<{
|
|
17
|
+
seed: z.ZodNumber;
|
|
18
|
+
rooms: z.ZodNumber;
|
|
19
|
+
cell: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
size: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
21
|
+
corridor: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
loops: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type DungeonSpec = z.input<typeof dungeonSchema>;
|
|
25
|
+
/** A box on the ground: [x, z, halfX, halfZ], m. */
|
|
26
|
+
export type Box = [x: number, z: number, hx: number, hz: number];
|
|
27
|
+
export interface DungeonRoom {
|
|
28
|
+
/** 0 is the start room. */
|
|
29
|
+
readonly id: number;
|
|
30
|
+
/** Centre, m. */
|
|
31
|
+
readonly x: number;
|
|
32
|
+
readonly z: number;
|
|
33
|
+
/** Half sizes, m. */
|
|
34
|
+
readonly hx: number;
|
|
35
|
+
readonly hz: number;
|
|
36
|
+
/** Doors from the start room (0 for the start). */
|
|
37
|
+
readonly depth: number;
|
|
38
|
+
/** Rooms this one has a corridor to. */
|
|
39
|
+
readonly links: readonly number[];
|
|
40
|
+
}
|
|
41
|
+
export interface DungeonLayout {
|
|
42
|
+
/** Pass to game({ field }). */
|
|
43
|
+
readonly field: {
|
|
44
|
+
readonly halfSize: number;
|
|
45
|
+
readonly pillars: [number, number, number][];
|
|
46
|
+
readonly walls: Box[];
|
|
47
|
+
readonly floors: Box[];
|
|
48
|
+
};
|
|
49
|
+
readonly rooms: readonly DungeonRoom[];
|
|
50
|
+
/** The start room (id 0, centred on [0, 0]). */
|
|
51
|
+
readonly start: DungeonRoom;
|
|
52
|
+
/** The deepest room: where the boss waits. */
|
|
53
|
+
readonly boss: DungeonRoom;
|
|
54
|
+
/** The room holding (x, z), or -1 in a corridor. */
|
|
55
|
+
roomAt(x: number, z: number): number;
|
|
56
|
+
}
|
|
57
|
+
/** Build a dungeon floor from a spec. Throws GameDefinitionError with readable lines on a bad spec. */
|
|
58
|
+
export declare function dungeon(spec: DungeonSpec): DungeonLayout;
|