@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,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netforge/engine/rules: the board genres (tower defense, idle, card
|
|
3
|
+
* battler, monster collector) on the room server's SimAdapter seam. Used by
|
|
4
|
+
* the room server, the headless harness and the template clients; game code
|
|
5
|
+
* declares content through @netforge/engine and never imports this.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./kit.js";
|
|
8
|
+
export { rulesAdapter } from "./adapter.js";
|
|
9
|
+
export type { RulesAdapterOptions, RulesWorld } from "./adapter.js";
|
|
10
|
+
export { playRules } from "./harness.js";
|
|
11
|
+
export type { RulesPlaytest } from "./harness.js";
|
|
12
|
+
export { genreFor, isRulesTemplate, labelledContent } from "./genres.js";
|
|
13
|
+
export * from "./tower.js";
|
|
14
|
+
export * from "./idle.js";
|
|
15
|
+
export * from "./cards.js";
|
|
16
|
+
export * from "./collector.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netforge/engine/rules: the board genres (tower defense, idle, card
|
|
3
|
+
* battler, monster collector) on the room server's SimAdapter seam. Used by
|
|
4
|
+
* the room server, the headless harness and the template clients; game code
|
|
5
|
+
* declares content through @netforge/engine and never imports this.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./kit.js";
|
|
8
|
+
export { rulesAdapter } from "./adapter.js";
|
|
9
|
+
export { playRules } from "./harness.js";
|
|
10
|
+
export { genreFor, isRulesTemplate, labelledContent } from "./genres.js";
|
|
11
|
+
export * from "./tower.js";
|
|
12
|
+
export * from "./idle.js";
|
|
13
|
+
export * from "./cards.js";
|
|
14
|
+
export * from "./collector.js";
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { EntitySchema, EntityState } from "../net/schema.js";
|
|
2
|
+
import type { InputCodec } from "../net/sim-adapter.js";
|
|
3
|
+
import type { GameEvent } from "../game/earn.js";
|
|
4
|
+
/** Fixed-point units per metre for avatars and entities. */
|
|
5
|
+
export declare const UNITS = 64;
|
|
6
|
+
/** One tick of a player's input. */
|
|
7
|
+
export interface RulesInput {
|
|
8
|
+
/** Stick, -127..127 (walking avatars). */
|
|
9
|
+
readonly mx: number;
|
|
10
|
+
readonly mz: number;
|
|
11
|
+
/** Cursor or target on the ground, 1/16 m, -32768..32767. */
|
|
12
|
+
readonly cx: number;
|
|
13
|
+
readonly cz: number;
|
|
14
|
+
/** The action this tick: 0 none; the genre names the rest. */
|
|
15
|
+
readonly act: number;
|
|
16
|
+
readonly a: number;
|
|
17
|
+
readonly b: number;
|
|
18
|
+
readonly c: number;
|
|
19
|
+
}
|
|
20
|
+
export declare const IDLE_INPUT: RulesInput;
|
|
21
|
+
/** 50 bits a tick idle, 99 with an action. */
|
|
22
|
+
export declare const rulesInputCodec: InputCodec<RulesInput>;
|
|
23
|
+
/** Entity kinds on the wire. */
|
|
24
|
+
export declare const RulesKind: {
|
|
25
|
+
readonly Avatar: 1;
|
|
26
|
+
readonly Unit: 2;
|
|
27
|
+
readonly Shot: 3;
|
|
28
|
+
readonly Structure: 4;
|
|
29
|
+
readonly Pickup: 5;
|
|
30
|
+
};
|
|
31
|
+
/** Field indices in RULES_SCHEMA. */
|
|
32
|
+
export declare const RF: {
|
|
33
|
+
readonly x: 0;
|
|
34
|
+
readonly z: 1;
|
|
35
|
+
readonly yaw: 2;
|
|
36
|
+
readonly type: 3;
|
|
37
|
+
readonly hp: 4;
|
|
38
|
+
readonly flags: 5;
|
|
39
|
+
readonly value: 6;
|
|
40
|
+
readonly aux: 7;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The wire schema every board genre shares: position (1/64 m), facing, a
|
|
44
|
+
* type, health, flags and two small values. Units interpolate their
|
|
45
|
+
* position; everything else steps.
|
|
46
|
+
*/
|
|
47
|
+
export declare const RULES_SCHEMA: EntitySchema;
|
|
48
|
+
/** A player's avatar (fixed point). The only state a client predicts. */
|
|
49
|
+
export interface Avatar {
|
|
50
|
+
x: number;
|
|
51
|
+
z: number;
|
|
52
|
+
/** u16 turn. */
|
|
53
|
+
yaw: number;
|
|
54
|
+
/** Look the player wears (store cosmetic id, 0 the default). */
|
|
55
|
+
cosmetic: number;
|
|
56
|
+
/** Genre flags drawn with the avatar (e.g. "ready"). */
|
|
57
|
+
flags: number;
|
|
58
|
+
}
|
|
59
|
+
/** Entity state for an avatar. Avatar entity ids are the player ids. */
|
|
60
|
+
export declare function avatarEntity(player: number, a: Avatar): EntityState;
|
|
61
|
+
/** Entity ids a genre uses start here (avatars take the player ids below). */
|
|
62
|
+
export declare const ENTITY_BASE = 65536;
|
|
63
|
+
export declare function entity(id: number, kind: number, owner: number, x: number, z: number, yawTurn: number, type: number, hp: number, flags?: number, value?: number, aux?: number): EntityState;
|
|
64
|
+
/** Seat bots and only them: player ids at 0xff00 and up (engine-sim's BOT_PLAYER_BASE). */
|
|
65
|
+
export declare const RULES_BOT_BASE = 65280;
|
|
66
|
+
export declare const isRulesBot: (p: number) => boolean;
|
|
67
|
+
/** mulberry32 on a state word kept in the genre's state (so a restored state continues the same stream). */
|
|
68
|
+
export interface Rng {
|
|
69
|
+
s: number;
|
|
70
|
+
}
|
|
71
|
+
export declare function rand(r: Rng): number;
|
|
72
|
+
/** 0..n-1. */
|
|
73
|
+
export declare function randInt(r: Rng, n: number): number;
|
|
74
|
+
/** Fisher-Yates in place. */
|
|
75
|
+
export declare function shuffle<T>(r: Rng, list: T[]): T[];
|
|
76
|
+
/** What a genre reads about the players from the adapter. */
|
|
77
|
+
export interface RulesContext {
|
|
78
|
+
/** A store upgrade's bonus for this player: the sum of step x level over the player's upgrades of `stat`. */
|
|
79
|
+
bonus(player: number, stat: string): number;
|
|
80
|
+
isBot(player: number): boolean;
|
|
81
|
+
}
|
|
82
|
+
export type RunOutcome = "won" | "lost";
|
|
83
|
+
/** Numbers the headless harness reports (HarnessReport), in the genre's own sense. */
|
|
84
|
+
export interface RulesStats {
|
|
85
|
+
/** Enemies defeated (TD creeps, duels won, wild creatures beaten). */
|
|
86
|
+
readonly kills: number;
|
|
87
|
+
/** Progress through the run: wave, tier, floor (1-based). */
|
|
88
|
+
readonly wave: number;
|
|
89
|
+
/** Highest level any player reached. */
|
|
90
|
+
readonly level: number;
|
|
91
|
+
/** Times a player lost something (lives lost, duels lost, a creature fainted). */
|
|
92
|
+
readonly deaths: number;
|
|
93
|
+
/** Most units alive at once. */
|
|
94
|
+
readonly peakUnits: number;
|
|
95
|
+
/** Players with at least one kill. */
|
|
96
|
+
readonly scorers: number;
|
|
97
|
+
/** Best single player's kills. */
|
|
98
|
+
readonly topScore: number;
|
|
99
|
+
}
|
|
100
|
+
/** A genre's own line in the playtest gate: a hard check or a scored part. */
|
|
101
|
+
export interface RulesCheck {
|
|
102
|
+
readonly ok: boolean;
|
|
103
|
+
/** The line the model reads when it fails, or what it measured. */
|
|
104
|
+
readonly line: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* One board genre. `S` is its whole authoritative state: integers, arrays
|
|
108
|
+
* and maps in insertion order, a PRNG word. Factories build one from the
|
|
109
|
+
* game's manifest (rules/genres.ts).
|
|
110
|
+
*/
|
|
111
|
+
export interface RulesGenre<S> {
|
|
112
|
+
/** The engine template it plays (game({ template })). */
|
|
113
|
+
readonly template: string;
|
|
114
|
+
readonly tickRate: number;
|
|
115
|
+
/** Seconds the end screen stays before a fresh run. */
|
|
116
|
+
readonly endSeconds: number;
|
|
117
|
+
/** Ticks between per-player run-state sends. */
|
|
118
|
+
readonly infoEveryTicks: number;
|
|
119
|
+
create(seed: number, ctx: RulesContext): S;
|
|
120
|
+
/** Where a joining player's avatar starts. */
|
|
121
|
+
spawn(s: S | null, player: number): {
|
|
122
|
+
x: number;
|
|
123
|
+
z: number;
|
|
124
|
+
};
|
|
125
|
+
/** Moves an avatar one tick from its own input and static world state only (both sides: the client predicts it). */
|
|
126
|
+
moveAvatar(a: Avatar, input: RulesInput): void;
|
|
127
|
+
join(s: S, player: number): void;
|
|
128
|
+
leave(s: S, player: number): void;
|
|
129
|
+
/** Applies a player's action (validated here: the server never trusts a client's numbers). */
|
|
130
|
+
act(s: S, player: number, input: RulesInput, avatar: Avatar, emit: (e: GameEvent) => void): void;
|
|
131
|
+
/**
|
|
132
|
+
* Server only, every tick, after each avatar moved: what the world does
|
|
133
|
+
* with where a player stands (a step into tall grass). It may move the
|
|
134
|
+
* avatar (a teleport home), which the client then reconciles.
|
|
135
|
+
*/
|
|
136
|
+
tend?(s: S, player: number, avatar: Avatar, emit: (e: GameEvent) => void): void;
|
|
137
|
+
/** One tick of the world. */
|
|
138
|
+
step(s: S, emit: (e: GameEvent) => void): void;
|
|
139
|
+
/** The run ended (null while it goes on). */
|
|
140
|
+
outcome(s: S): RunOutcome | null;
|
|
141
|
+
/** What `player` may see (null: what everyone may see). JSON-safe. Never another player's secrets. */
|
|
142
|
+
view(s: S, player: number | null): Record<string, unknown>;
|
|
143
|
+
/** Networked entities other than avatars (ids from 0). */
|
|
144
|
+
entities(s: S, out: EntityState[]): void;
|
|
145
|
+
/** Seat bots wanted with `humans` people in the room (bots never earn). Omit for none. */
|
|
146
|
+
seats?(s: S, humans: number): number;
|
|
147
|
+
/** A bot's (or a playtest player's) input this tick. `skill` 0..1 (playtests use 1). */
|
|
148
|
+
bot(s: S, player: number, avatar: Avatar, tick: number, skill: number): RulesInput;
|
|
149
|
+
stats(s: S): RulesStats;
|
|
150
|
+
/** Changes whenever anything happens (the softlock detector). */
|
|
151
|
+
activity(s: S): number;
|
|
152
|
+
/** The genre's own playtest checks on one playtest run (the gate fails the draft on any that is not ok in the squad run). */
|
|
153
|
+
checks(run: RulesHarnessRun): RulesCheck[];
|
|
154
|
+
/** How far the playtest got through the genre's arc, 0..1 (the gate's progression score). */
|
|
155
|
+
progress(run: RulesHarnessRun): number;
|
|
156
|
+
}
|
|
157
|
+
/** One harness run's numbers, for a genre's checks. */
|
|
158
|
+
export interface RulesHarnessRun {
|
|
159
|
+
readonly seconds: number;
|
|
160
|
+
readonly players: number;
|
|
161
|
+
readonly stats: RulesStats;
|
|
162
|
+
readonly runs: {
|
|
163
|
+
readonly won: number;
|
|
164
|
+
readonly lost: number;
|
|
165
|
+
};
|
|
166
|
+
readonly earned: Readonly<Record<string, number>>;
|
|
167
|
+
/** Seconds until the first earn award (null if none). */
|
|
168
|
+
readonly firstEarnSeconds: number | null;
|
|
169
|
+
/** Seconds the first run lasted (null if it never ended). */
|
|
170
|
+
readonly firstRunSeconds: number | null;
|
|
171
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Fixed-point units per metre for avatars and entities. */
|
|
2
|
+
export const UNITS = 64;
|
|
3
|
+
export const IDLE_INPUT = { mx: 0, mz: 0, cx: 0, cz: 0, act: 0, a: 0, b: 0, c: 0 };
|
|
4
|
+
const clampInt = (v, lo, hi) => (Number.isFinite(v) ? Math.max(lo, Math.min(hi, Math.round(v))) : 0);
|
|
5
|
+
/** 50 bits a tick idle, 99 with an action. */
|
|
6
|
+
export const rulesInputCodec = {
|
|
7
|
+
idle: IDLE_INPUT,
|
|
8
|
+
write(w, i) {
|
|
9
|
+
w.writeSigned(clampInt(i.mx, -127, 127), 8);
|
|
10
|
+
w.writeSigned(clampInt(i.mz, -127, 127), 8);
|
|
11
|
+
w.writeSigned(clampInt(i.cx, -32768, 32767), 16);
|
|
12
|
+
w.writeSigned(clampInt(i.cz, -32768, 32767), 16);
|
|
13
|
+
const act = clampInt(i.act, 0, 255);
|
|
14
|
+
w.writeBool(act !== 0);
|
|
15
|
+
if (act !== 0) {
|
|
16
|
+
w.writeBits(act, 8);
|
|
17
|
+
w.writeBits(clampInt(i.a, 0, 0xffff), 16);
|
|
18
|
+
w.writeBits(clampInt(i.b, 0, 0xffff), 16);
|
|
19
|
+
w.writeBits(clampInt(i.c, 0, 0xffff), 16);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
read(r) {
|
|
23
|
+
const mx = r.readSigned(8);
|
|
24
|
+
const mz = r.readSigned(8);
|
|
25
|
+
const cx = r.readSigned(16);
|
|
26
|
+
const cz = r.readSigned(16);
|
|
27
|
+
if (!r.readBool())
|
|
28
|
+
return { mx, mz, cx, cz, act: 0, a: 0, b: 0, c: 0 };
|
|
29
|
+
return { mx, mz, cx, cz, act: r.readBits(8), a: r.readBits(16), b: r.readBits(16), c: r.readBits(16) };
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
/** Entity kinds on the wire. */
|
|
33
|
+
export const RulesKind = { Avatar: 1, Unit: 2, Shot: 3, Structure: 4, Pickup: 5 };
|
|
34
|
+
/** Field indices in RULES_SCHEMA. */
|
|
35
|
+
export const RF = { x: 0, z: 1, yaw: 2, type: 3, hp: 4, flags: 5, value: 6, aux: 7 };
|
|
36
|
+
/**
|
|
37
|
+
* The wire schema every board genre shares: position (1/64 m), facing, a
|
|
38
|
+
* type, health, flags and two small values. Units interpolate their
|
|
39
|
+
* position; everything else steps.
|
|
40
|
+
*/
|
|
41
|
+
export const RULES_SCHEMA = {
|
|
42
|
+
fields: [
|
|
43
|
+
{ name: "x", kind: "int", bits: 20, deltaBits: 10 },
|
|
44
|
+
{ name: "z", kind: "int", bits: 20, deltaBits: 10 },
|
|
45
|
+
{ name: "yaw", kind: "angle16", deltaBits: 9 },
|
|
46
|
+
{ name: "type", kind: "uint", bits: 8, interp: "step" },
|
|
47
|
+
{ name: "hp", kind: "uint", bits: 20, interp: "step", deltaBits: 8 },
|
|
48
|
+
{ name: "flags", kind: "uint", bits: 8, interp: "step" },
|
|
49
|
+
{ name: "value", kind: "uint", bits: 16, interp: "step", deltaBits: 6 },
|
|
50
|
+
{ name: "aux", kind: "uint", bits: 16, interp: "step", deltaBits: 6 },
|
|
51
|
+
],
|
|
52
|
+
xField: RF.x,
|
|
53
|
+
yField: RF.z,
|
|
54
|
+
unitsPerWorld: UNITS,
|
|
55
|
+
};
|
|
56
|
+
/** Entity state for an avatar. Avatar entity ids are the player ids. */
|
|
57
|
+
export function avatarEntity(player, a) {
|
|
58
|
+
return { id: player, kind: RulesKind.Avatar, owner: player, values: [a.x, a.z, (a.yaw / 65536) * Math.PI * 2, 0, 0, a.flags & 0xff, a.cosmetic & 0xffff, 0] };
|
|
59
|
+
}
|
|
60
|
+
/** Entity ids a genre uses start here (avatars take the player ids below). */
|
|
61
|
+
export const ENTITY_BASE = 0x10000;
|
|
62
|
+
export function entity(id, kind, owner, x, z, yawTurn, type, hp, flags = 0, value = 0, aux = 0) {
|
|
63
|
+
return { id: ENTITY_BASE + id, kind, owner, values: [x, z, (yawTurn / 65536) * Math.PI * 2, type, Math.max(0, Math.min(0xfffff, Math.round(hp))), flags & 0xff, value & 0xffff, aux & 0xffff] };
|
|
64
|
+
}
|
|
65
|
+
/** Seat bots and only them: player ids at 0xff00 and up (engine-sim's BOT_PLAYER_BASE). */
|
|
66
|
+
export const RULES_BOT_BASE = 0xff00;
|
|
67
|
+
export const isRulesBot = (p) => p >= RULES_BOT_BASE;
|
|
68
|
+
export function rand(r) {
|
|
69
|
+
r.s = (r.s + 0x6d2b79f5) | 0;
|
|
70
|
+
let t = r.s;
|
|
71
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
72
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
73
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
74
|
+
}
|
|
75
|
+
/** 0..n-1. */
|
|
76
|
+
export function randInt(r, n) {
|
|
77
|
+
return Math.floor(rand(r) * n) % Math.max(1, n);
|
|
78
|
+
}
|
|
79
|
+
/** Fisher-Yates in place. */
|
|
80
|
+
export function shuffle(r, list) {
|
|
81
|
+
for (let i = list.length - 1; i > 0; i--) {
|
|
82
|
+
const j = randInt(r, i + 1);
|
|
83
|
+
const t = list[i];
|
|
84
|
+
list[i] = list[j];
|
|
85
|
+
list[j] = t;
|
|
86
|
+
}
|
|
87
|
+
return list;
|
|
88
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tower defense genre (template "tower-defense"): a co-op board where
|
|
3
|
+
* up to four players build and upgrade towers along one road while waves of
|
|
4
|
+
* creeps walk it toward the spire. Shared lives, gold of your own, bounties
|
|
5
|
+
* to the tower's builder and a share to everyone else.
|
|
6
|
+
*
|
|
7
|
+
* Fixed point: Q units per tile (a tile is 2 m), integer creep progress
|
|
8
|
+
* along the road, integer ranges compared squared. Targeting is "first"
|
|
9
|
+
* (the creep furthest along), ties to the lower id, so the run replays
|
|
10
|
+
* exactly from its inputs.
|
|
11
|
+
*/
|
|
12
|
+
import type { GameManifest } from "../api/registry.js";
|
|
13
|
+
import { type TowerDef } from "../api/tower.js";
|
|
14
|
+
import type { RulesContext, RulesGenre } from "./kit.js";
|
|
15
|
+
/** Fixed-point units per tile. */
|
|
16
|
+
export declare const Q = 256;
|
|
17
|
+
/** Metres per tile. */
|
|
18
|
+
export declare const TILE_M = 2;
|
|
19
|
+
/** Actions (RulesInput.act). */
|
|
20
|
+
export declare const TowerAct: {
|
|
21
|
+
readonly Build: 1;
|
|
22
|
+
readonly Upgrade: 2;
|
|
23
|
+
readonly Sell: 3;
|
|
24
|
+
readonly Ready: 4;
|
|
25
|
+
};
|
|
26
|
+
/** Creep flags on the wire. */
|
|
27
|
+
export declare const CreepFlag: {
|
|
28
|
+
readonly Slowed: 1;
|
|
29
|
+
readonly Boss: 2;
|
|
30
|
+
readonly Dead: 4;
|
|
31
|
+
readonly Leaked: 8;
|
|
32
|
+
};
|
|
33
|
+
interface Creep {
|
|
34
|
+
id: number;
|
|
35
|
+
type: number;
|
|
36
|
+
hp: number;
|
|
37
|
+
maxHp: number;
|
|
38
|
+
/** Progress along the road, Q units. */
|
|
39
|
+
d: number;
|
|
40
|
+
/** Q units a tick at full speed. */
|
|
41
|
+
speed: number;
|
|
42
|
+
slowTicks: number;
|
|
43
|
+
slow: number;
|
|
44
|
+
value: number;
|
|
45
|
+
dmg: number;
|
|
46
|
+
boss: boolean;
|
|
47
|
+
/** Ticks left as a corpse (dead or leaked); 0 while alive. */
|
|
48
|
+
gone: number;
|
|
49
|
+
flags: number;
|
|
50
|
+
/** Who dealt the killing blow (tower owner), for the feed. */
|
|
51
|
+
by: number;
|
|
52
|
+
}
|
|
53
|
+
interface Tower {
|
|
54
|
+
id: number;
|
|
55
|
+
type: number;
|
|
56
|
+
owner: number;
|
|
57
|
+
tx: number;
|
|
58
|
+
ty: number;
|
|
59
|
+
level: number;
|
|
60
|
+
cd: number;
|
|
61
|
+
spent: number;
|
|
62
|
+
/** Shots fired (u16 wrap): the client draws a tracer when it changes. */
|
|
63
|
+
shots: number;
|
|
64
|
+
/** Local id of the last creep shot. */
|
|
65
|
+
target: number;
|
|
66
|
+
yaw: number;
|
|
67
|
+
kills: number;
|
|
68
|
+
}
|
|
69
|
+
interface TowerPlayer {
|
|
70
|
+
gold: number;
|
|
71
|
+
kills: number;
|
|
72
|
+
ready: boolean;
|
|
73
|
+
built: number;
|
|
74
|
+
}
|
|
75
|
+
interface Spawn {
|
|
76
|
+
tick: number;
|
|
77
|
+
type: number;
|
|
78
|
+
}
|
|
79
|
+
export interface TowerState {
|
|
80
|
+
rng: {
|
|
81
|
+
s: number;
|
|
82
|
+
};
|
|
83
|
+
ctx: RulesContext;
|
|
84
|
+
tick: number;
|
|
85
|
+
wave: number;
|
|
86
|
+
phase: "build" | "wave";
|
|
87
|
+
countdown: number;
|
|
88
|
+
lives: number;
|
|
89
|
+
livesMax: number;
|
|
90
|
+
/** Lives the team bought (maxHealth upgrades) already added. */
|
|
91
|
+
livesBought: Map<number, number>;
|
|
92
|
+
queue: Spawn[];
|
|
93
|
+
waveTick: number;
|
|
94
|
+
creeps: Creep[];
|
|
95
|
+
towers: Tower[];
|
|
96
|
+
players: Map<number, TowerPlayer>;
|
|
97
|
+
nextId: number;
|
|
98
|
+
kills: number;
|
|
99
|
+
leaks: number;
|
|
100
|
+
peak: number;
|
|
101
|
+
won: boolean;
|
|
102
|
+
/** Bumped whenever anything happens (softlock detector). */
|
|
103
|
+
activity: number;
|
|
104
|
+
}
|
|
105
|
+
interface TowerStats {
|
|
106
|
+
damage: number;
|
|
107
|
+
rate: number;
|
|
108
|
+
range: number;
|
|
109
|
+
radius: number;
|
|
110
|
+
slow: number;
|
|
111
|
+
slowSeconds: number;
|
|
112
|
+
chains: number;
|
|
113
|
+
}
|
|
114
|
+
export interface CompiledTower {
|
|
115
|
+
readonly id: string;
|
|
116
|
+
readonly def: TowerDef;
|
|
117
|
+
/** Stats at each level (0 = built). */
|
|
118
|
+
readonly levels: readonly TowerStats[];
|
|
119
|
+
/** Cost of each upgrade (index 0 buys level 1). */
|
|
120
|
+
readonly upgradeCost: readonly number[];
|
|
121
|
+
}
|
|
122
|
+
export interface CompiledBoard {
|
|
123
|
+
readonly w: number;
|
|
124
|
+
readonly h: number;
|
|
125
|
+
/** Waypoints, tile coords. */
|
|
126
|
+
readonly path: readonly (readonly [number, number])[];
|
|
127
|
+
/** Cumulative road length at each waypoint, Q units. */
|
|
128
|
+
readonly cum: readonly number[];
|
|
129
|
+
readonly length: number;
|
|
130
|
+
readonly road: ReadonlySet<number>;
|
|
131
|
+
readonly blocked: ReadonlySet<number>;
|
|
132
|
+
}
|
|
133
|
+
/** Tile key. */
|
|
134
|
+
export declare const tileKey: (x: number, y: number) => number;
|
|
135
|
+
/** A tile's centre in metres, board centred on the origin. */
|
|
136
|
+
export declare function tileCenter(b: Pick<CompiledBoard, "w" | "h">, tx: number, ty: number): {
|
|
137
|
+
x: number;
|
|
138
|
+
z: number;
|
|
139
|
+
};
|
|
140
|
+
/** The tile under a point in metres, or null off the board. */
|
|
141
|
+
export declare function tileAt(b: Pick<CompiledBoard, "w" | "h">, x: number, z: number): {
|
|
142
|
+
tx: number;
|
|
143
|
+
ty: number;
|
|
144
|
+
} | null;
|
|
145
|
+
export declare function compileBoard(def: {
|
|
146
|
+
size: readonly [number, number];
|
|
147
|
+
path: readonly (readonly [number, number])[];
|
|
148
|
+
blocked: readonly (readonly [number, number])[];
|
|
149
|
+
}): CompiledBoard;
|
|
150
|
+
/** Position on the road at progress `d` (Q units), in Q units with the board's corner at 0, and the heading (u16 turn). */
|
|
151
|
+
export declare function roadPoint(b: CompiledBoard, d: number): {
|
|
152
|
+
qx: number;
|
|
153
|
+
qy: number;
|
|
154
|
+
yaw: number;
|
|
155
|
+
};
|
|
156
|
+
/** Everything a tower defense game declared, compiled (shared by the room, the harness and the client). */
|
|
157
|
+
export interface CompiledTowerGame {
|
|
158
|
+
readonly board: CompiledBoard;
|
|
159
|
+
readonly towers: readonly CompiledTower[];
|
|
160
|
+
readonly creeps: readonly {
|
|
161
|
+
id: string;
|
|
162
|
+
model: string;
|
|
163
|
+
health: number;
|
|
164
|
+
speed: number;
|
|
165
|
+
value: number;
|
|
166
|
+
damage: number;
|
|
167
|
+
boss: boolean;
|
|
168
|
+
}[];
|
|
169
|
+
readonly lives: number;
|
|
170
|
+
readonly gold: number;
|
|
171
|
+
readonly prep: number;
|
|
172
|
+
readonly breather: number;
|
|
173
|
+
readonly share: number;
|
|
174
|
+
readonly waveGold: {
|
|
175
|
+
base: number;
|
|
176
|
+
perWave: number;
|
|
177
|
+
};
|
|
178
|
+
readonly refund: number;
|
|
179
|
+
readonly totalWaves: number;
|
|
180
|
+
readonly healthGrowth: number;
|
|
181
|
+
readonly groups: readonly {
|
|
182
|
+
type: number;
|
|
183
|
+
from: number;
|
|
184
|
+
until: number;
|
|
185
|
+
every: number;
|
|
186
|
+
count: number;
|
|
187
|
+
grow: number;
|
|
188
|
+
gap: number;
|
|
189
|
+
delay: number;
|
|
190
|
+
}[];
|
|
191
|
+
}
|
|
192
|
+
export declare function compileTowerGame(m: GameManifest): CompiledTowerGame;
|
|
193
|
+
/** The spawns of wave `n` (1-based), by tick offset into the wave. */
|
|
194
|
+
export declare function waveSpawns(g: CompiledTowerGame, n: number, tickRate: number): Spawn[];
|
|
195
|
+
/** What the next wave brings, by creep type (the HUD's preview). */
|
|
196
|
+
export declare function wavePreview(g: CompiledTowerGame, n: number): {
|
|
197
|
+
type: number;
|
|
198
|
+
count: number;
|
|
199
|
+
}[];
|
|
200
|
+
export declare function roadCoverage(b: CompiledBoard, rangeQ: number): Int32Array;
|
|
201
|
+
/**
|
|
202
|
+
* The best free tile for a tower of `type` (the most road in reach), with a
|
|
203
|
+
* per-player tie-break and `jitter` (0 for the sharpest choice). Null when
|
|
204
|
+
* no free tile reaches the road. Used by the playtest bots and the
|
|
205
|
+
* client's autopilot alike.
|
|
206
|
+
*/
|
|
207
|
+
export declare function bestTowerTile(g: CompiledTowerGame, occupied: ReadonlySet<number>, type: number, salt: number, jitter?: number): {
|
|
208
|
+
tx: number;
|
|
209
|
+
ty: number;
|
|
210
|
+
} | null;
|
|
211
|
+
export declare function towerGenre(m: GameManifest): RulesGenre<TowerState> & {
|
|
212
|
+
readonly game: CompiledTowerGame;
|
|
213
|
+
};
|
|
214
|
+
export {};
|