@netforge/template-party 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/AGENTS.md +63 -0
- package/CLAUDE.md +11 -0
- package/LICENSE +21 -0
- package/NOTICE +32 -0
- package/README.md +74 -2
- package/client/debris.ts +267 -0
- package/client/decor.ts +13 -0
- package/client/host.ts +96 -0
- package/client/hud.ts +576 -0
- package/client/icons.ts +42 -0
- package/client/index.html +277 -0
- package/client/input.ts +172 -0
- package/client/library.ts +250 -0
- package/client/main.ts +607 -0
- package/client/names.ts +47 -0
- package/client/palette.ts +7 -0
- package/client/scene.ts +941 -0
- package/client/stage.ts +266 -0
- package/client/stake.ts +101 -0
- package/client/threats.ts +157 -0
- package/llms-full.txt +716 -0
- package/llms.txt +375 -0
- package/package.json +82 -3
- package/src/game.ts +326 -0
- package/src/manifest.ts +38 -0
- package/src/store.ts +107 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fallback shapes. Every library id the game
|
|
3
|
+
* references is drawn from the per-style asset library
|
|
4
|
+
* (@netforge/asset-library: real CC0 models, see scene.ts); these
|
|
5
|
+
* primitives are what the scene shows until a model loads, and what it
|
|
6
|
+
* keeps when the library is not built or not reachable, so the game always
|
|
7
|
+
* draws. The keys are the template's original ids, which the library keeps
|
|
8
|
+
* as aliases.
|
|
9
|
+
*/
|
|
10
|
+
import { assetById } from "@netforge/asset-library";
|
|
11
|
+
import {
|
|
12
|
+
BoxGeometry,
|
|
13
|
+
BufferGeometry,
|
|
14
|
+
CapsuleGeometry,
|
|
15
|
+
Color,
|
|
16
|
+
ConeGeometry,
|
|
17
|
+
CylinderGeometry,
|
|
18
|
+
DodecahedronGeometry,
|
|
19
|
+
IcosahedronGeometry,
|
|
20
|
+
LatheGeometry,
|
|
21
|
+
Material,
|
|
22
|
+
MeshStandardMaterial,
|
|
23
|
+
OctahedronGeometry,
|
|
24
|
+
TetrahedronGeometry,
|
|
25
|
+
Vector2,
|
|
26
|
+
} from "three/webgpu";
|
|
27
|
+
|
|
28
|
+
export interface LibraryAsset {
|
|
29
|
+
readonly geometry: BufferGeometry;
|
|
30
|
+
readonly material: Material;
|
|
31
|
+
/** Height of the model's origin above the floor, m. */
|
|
32
|
+
readonly lift: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mat = (color: number, emissive = 0x000000, emissiveIntensity = 1, roughness = 0.7) =>
|
|
36
|
+
new MeshStandardMaterial({ color: new Color(color), emissive: new Color(emissive), emissiveIntensity, roughness, flatShading: true });
|
|
37
|
+
|
|
38
|
+
function bell(): BufferGeometry {
|
|
39
|
+
const pts = [
|
|
40
|
+
[0.0, 0.0],
|
|
41
|
+
[1.0, 0.0],
|
|
42
|
+
[0.95, 0.25],
|
|
43
|
+
[0.8, 0.6],
|
|
44
|
+
[0.7, 1.1],
|
|
45
|
+
[0.62, 1.5],
|
|
46
|
+
[0.45, 1.85],
|
|
47
|
+
[0.2, 2.0],
|
|
48
|
+
[0.0, 2.05],
|
|
49
|
+
].map(([x, y]) => new Vector2(x, y));
|
|
50
|
+
return new LatheGeometry(pts, 9);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const builders: Record<string, (radius: number) => LibraryAsset> = {
|
|
54
|
+
shade: (r) => {
|
|
55
|
+
const g = new ConeGeometry(r * 1.05, r * 3, 5);
|
|
56
|
+
g.translate(0, r * 1.5, 0);
|
|
57
|
+
return { geometry: g, material: mat(0xff9a4a, 0x8a3410, 0.9, 0.55), lift: 0 };
|
|
58
|
+
},
|
|
59
|
+
gnat: (r) => {
|
|
60
|
+
const g = new TetrahedronGeometry(r * 1.4);
|
|
61
|
+
g.translate(0, r * 2.2, 0);
|
|
62
|
+
return { geometry: g, material: mat(0xc6ff6b, 0x4c8a10, 1, 0.5), lift: 0 };
|
|
63
|
+
},
|
|
64
|
+
husk: (r) => {
|
|
65
|
+
const g = new DodecahedronGeometry(r * 1.1);
|
|
66
|
+
g.translate(0, r * 1.1, 0);
|
|
67
|
+
return { geometry: g, material: mat(0xe0904a, 0x6a2c08, 0.8, 0.6), lift: 0 };
|
|
68
|
+
},
|
|
69
|
+
brute: (r) => {
|
|
70
|
+
const g = new BoxGeometry(r * 1.8, r * 2.2, r * 1.4);
|
|
71
|
+
g.translate(0, r * 1.1, 0);
|
|
72
|
+
return { geometry: g, material: mat(0xff4d5e, 0x7a0c18, 0.9, 0.6), lift: 0 };
|
|
73
|
+
},
|
|
74
|
+
bell_warden: (r) => {
|
|
75
|
+
const g = bell();
|
|
76
|
+
g.scale(r, r * 1.2, r);
|
|
77
|
+
return { geometry: g, material: mat(0xe8c27a, 0x6a4210, 0.9, 0.35), lift: 0 };
|
|
78
|
+
},
|
|
79
|
+
// Roles: each silhouette says what it does.
|
|
80
|
+
spitter: (r) => {
|
|
81
|
+
// A squat, open-mouthed bulb: it keeps its distance and spits.
|
|
82
|
+
const g = new IcosahedronGeometry(r * 1.1, 0);
|
|
83
|
+
g.scale(1, 0.8, 1);
|
|
84
|
+
g.translate(0, r * 1.0, 0);
|
|
85
|
+
return { geometry: g, material: mat(0x5affc8, 0x0f8a6a, 1.1, 0.5), lift: 0 };
|
|
86
|
+
},
|
|
87
|
+
ram: (r) => {
|
|
88
|
+
// A wedge pointing where it charges.
|
|
89
|
+
const g = new ConeGeometry(r * 1.2, r * 3, 4);
|
|
90
|
+
g.rotateX(-Math.PI / 2);
|
|
91
|
+
g.translate(0, r * 1.1, 0);
|
|
92
|
+
return { geometry: g, material: mat(0xff7a3c, 0x8a2a00, 1, 0.5), lift: 0 };
|
|
93
|
+
},
|
|
94
|
+
bloat: (r) => {
|
|
95
|
+
// A swollen ember: it walks up to you and bursts.
|
|
96
|
+
const g = new DodecahedronGeometry(r * 1.25);
|
|
97
|
+
g.translate(0, r * 1.25, 0);
|
|
98
|
+
return { geometry: g, material: mat(0xffc04d, 0xff4a00, 1.8, 0.4), lift: 0 };
|
|
99
|
+
},
|
|
100
|
+
aegis: (r) => {
|
|
101
|
+
const g = new OctahedronGeometry(r * 1.3);
|
|
102
|
+
g.scale(0.8, 1.3, 0.8);
|
|
103
|
+
g.translate(0, r * 1.6, 0);
|
|
104
|
+
return { geometry: g, material: mat(0x6fd3ff, 0x1a5aa8, 1.2, 0.4), lift: 0 };
|
|
105
|
+
},
|
|
106
|
+
mender: (r) => {
|
|
107
|
+
const g = new CapsuleGeometry(r * 0.7, r * 1.4, 4, 8);
|
|
108
|
+
g.translate(0, r * 1.4, 0);
|
|
109
|
+
return { geometry: g, material: mat(0x9dff7a, 0x2a8a10, 1.2, 0.5), lift: 0 };
|
|
110
|
+
},
|
|
111
|
+
brood: (r) => {
|
|
112
|
+
const g = new IcosahedronGeometry(r * 1.2, 1);
|
|
113
|
+
g.translate(0, r * 1.2, 0);
|
|
114
|
+
return { geometry: g, material: mat(0xb07aff, 0x4a1aa8, 1, 0.55), lift: 0 };
|
|
115
|
+
},
|
|
116
|
+
caller: (r) => {
|
|
117
|
+
const g = new ConeGeometry(r * 0.9, r * 4, 6);
|
|
118
|
+
g.translate(0, r * 2, 0);
|
|
119
|
+
return { geometry: g, material: mat(0xd07aff, 0x6a10a8, 1.3, 0.45), lift: 0 };
|
|
120
|
+
},
|
|
121
|
+
ash_matron: (r) => {
|
|
122
|
+
const g = bell();
|
|
123
|
+
g.scale(r * 0.9, r * 1.4, r * 0.9);
|
|
124
|
+
return { geometry: g, material: mat(0xc9b6a0, 0x5a3a20, 0.9, 0.5), lift: 0 };
|
|
125
|
+
},
|
|
126
|
+
gloam_ram: (r) => {
|
|
127
|
+
const g = new ConeGeometry(r * 1.1, r * 2.6, 5);
|
|
128
|
+
g.rotateX(-Math.PI / 2);
|
|
129
|
+
g.translate(0, r * 1.0, 0);
|
|
130
|
+
return { geometry: g, material: mat(0x7a5cff, 0x2a10a8, 1.1, 0.4), lift: 0 };
|
|
131
|
+
},
|
|
132
|
+
ember_small: () => {
|
|
133
|
+
const g = new OctahedronGeometry(0.16);
|
|
134
|
+
return { geometry: g, material: mat(0xffc04d, 0xff8a00, 1.6, 0.3), lift: 0.35 };
|
|
135
|
+
},
|
|
136
|
+
ember_mid: () => {
|
|
137
|
+
const g = new OctahedronGeometry(0.24);
|
|
138
|
+
return { geometry: g, material: mat(0x7ad7ff, 0x1e90ff, 1.8, 0.3), lift: 0.4 };
|
|
139
|
+
},
|
|
140
|
+
ember_large: () => {
|
|
141
|
+
const g = new IcosahedronGeometry(0.4);
|
|
142
|
+
return { geometry: g, material: mat(0xff7ad9, 0xff2fb0, 2, 0.3), lift: 0.5 };
|
|
143
|
+
},
|
|
144
|
+
keeper_01: () => {
|
|
145
|
+
const g = new CapsuleGeometry(0.4, 1.0, 4, 10);
|
|
146
|
+
return { geometry: g, material: mat(0xffffff, 0x000000, 1, 0.5), lift: 0 };
|
|
147
|
+
},
|
|
148
|
+
// Pickups and props: shapes for when the library's models are not fetched.
|
|
149
|
+
cell_heal: () => {
|
|
150
|
+
const g = new BoxGeometry(0.34, 0.12, 0.34);
|
|
151
|
+
return { geometry: g, material: mat(0x7dff9a, 0x1f9a3a, 1.6, 0.3), lift: 0.45 };
|
|
152
|
+
},
|
|
153
|
+
crystal: () => {
|
|
154
|
+
const g = new OctahedronGeometry(0.3);
|
|
155
|
+
g.scale(0.7, 1.3, 0.7);
|
|
156
|
+
return { geometry: g, material: mat(0x9fd8ff, 0x2a6aff, 1.8, 0.2), lift: 0.55 };
|
|
157
|
+
},
|
|
158
|
+
grenade: () => {
|
|
159
|
+
const g = new DodecahedronGeometry(0.24);
|
|
160
|
+
return { geometry: g, material: mat(0xff8a4a, 0xff3d00, 1.8, 0.3), lift: 0.45 };
|
|
161
|
+
},
|
|
162
|
+
chest: () => {
|
|
163
|
+
const g = new BoxGeometry(0.7, 0.45, 0.45);
|
|
164
|
+
g.translate(0, 0.22, 0);
|
|
165
|
+
return { geometry: g, material: mat(0xc9963a, 0x6a4210, 1.2, 0.4), lift: 0 };
|
|
166
|
+
},
|
|
167
|
+
barrel: (r) => {
|
|
168
|
+
const g = new CylinderGeometry(r * 0.8, r * 0.9, r * 2, 8);
|
|
169
|
+
g.translate(0, r, 0);
|
|
170
|
+
return { geometry: g, material: mat(0x8a5a34, 0x2a1408, 0.5, 0.7), lift: 0 };
|
|
171
|
+
},
|
|
172
|
+
campfire: (r) => {
|
|
173
|
+
const g = new ConeGeometry(r * 0.9, r * 1.4, 6);
|
|
174
|
+
g.translate(0, r * 0.7, 0);
|
|
175
|
+
return { geometry: g, material: mat(0xff9a40, 0xff5a00, 2, 0.4), lift: 0 };
|
|
176
|
+
},
|
|
177
|
+
fox: (r) => {
|
|
178
|
+
const g = new ConeGeometry(r * 0.9, r * 2.4, 4);
|
|
179
|
+
g.rotateX(-Math.PI / 2);
|
|
180
|
+
g.translate(0, r * 0.9, 0);
|
|
181
|
+
return { geometry: g, material: mat(0xffd36b, 0xffa000, 2, 0.3), lift: 0 };
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** A shape for any library id: its alias's builder, else one by body plan. */
|
|
186
|
+
function byPlan(id: string, r: number): LibraryAsset | null {
|
|
187
|
+
const a = assetById(id);
|
|
188
|
+
if (!a) return null;
|
|
189
|
+
for (const alias of a.aliases) {
|
|
190
|
+
const b = builders[alias.split(".")[2] ?? ""];
|
|
191
|
+
if (b) return b(r);
|
|
192
|
+
}
|
|
193
|
+
const name = a.id.split(".")[2] ?? "";
|
|
194
|
+
if (name.startsWith("gem_")) return builders[name === "gem_small" ? "ember_small" : name === "gem_mid" ? "ember_mid" : "ember_large"]?.(r) ?? null;
|
|
195
|
+
switch (a.plan) {
|
|
196
|
+
case "flyer":
|
|
197
|
+
return builders.shade?.(r) ?? null;
|
|
198
|
+
case "blob": {
|
|
199
|
+
const g = new IcosahedronGeometry(r * 1.1, 1);
|
|
200
|
+
g.scale(1, 0.75, 1);
|
|
201
|
+
g.translate(0, r * 0.8, 0);
|
|
202
|
+
return { geometry: g, material: mat(0x7bd88f, 0x1e5a2a, 0.6), lift: 0 };
|
|
203
|
+
}
|
|
204
|
+
case "quadruped":
|
|
205
|
+
case "wheeled": {
|
|
206
|
+
const g = new BoxGeometry(r * 1.4, r * 1.2, r * 2.2);
|
|
207
|
+
g.translate(0, r * 0.6, 0);
|
|
208
|
+
return { geometry: g, material: mat(0xc9a27a, 0x3a2a10, 0.5), lift: 0 };
|
|
209
|
+
}
|
|
210
|
+
case "biped": {
|
|
211
|
+
const g = new CapsuleGeometry(r * 0.8, r * 1.6, 4, 8);
|
|
212
|
+
g.translate(0, r * 1.6, 0);
|
|
213
|
+
return { geometry: g, material: mat(0xe0904a, 0x6a2c08, 0.6), lift: 0 };
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Resolve a library id ("style.lowpoly.ghost") to its fallback shape for an entity of `radius` m. Unknown ids get a marked box. */
|
|
221
|
+
export function resolveAsset(id: string, radius = 0.35): LibraryAsset {
|
|
222
|
+
const name = id.split(".")[2] ?? "";
|
|
223
|
+
const b = builders[name] ?? null;
|
|
224
|
+
if (b) return b(radius);
|
|
225
|
+
const p = byPlan(id, radius);
|
|
226
|
+
if (p) return p;
|
|
227
|
+
const g = new BoxGeometry(radius * 2, radius * 2, radius * 2);
|
|
228
|
+
g.translate(0, radius, 0);
|
|
229
|
+
return { geometry: g, material: mat(0xff00ff, 0x400040), lift: 0 };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Projectile, blade and effect meshes (template machinery, not library assets). */
|
|
233
|
+
export const fx = {
|
|
234
|
+
bolt: () => {
|
|
235
|
+
const g = new CylinderGeometry(0.06, 0.12, 0.9, 5);
|
|
236
|
+
g.rotateX(Math.PI / 2);
|
|
237
|
+
return { geometry: g, material: mat(0xffe08a, 0xffb000, 2.2, 0.2) };
|
|
238
|
+
},
|
|
239
|
+
lance: () => {
|
|
240
|
+
const g = new CylinderGeometry(0.05, 0.16, 1.6, 6);
|
|
241
|
+
g.rotateX(Math.PI / 2);
|
|
242
|
+
return { geometry: g, material: mat(0xff8a5c, 0xff3d00, 2.4, 0.2) };
|
|
243
|
+
},
|
|
244
|
+
blade: () => {
|
|
245
|
+
const g = new OctahedronGeometry(0.32);
|
|
246
|
+
g.scale(1.4, 0.35, 0.7);
|
|
247
|
+
return { geometry: g, material: mat(0xfff2c4, 0xffd36b, 1.8, 0.2) };
|
|
248
|
+
},
|
|
249
|
+
shard: () => new TetrahedronGeometry(0.12),
|
|
250
|
+
};
|