@adatechnology/game-floorplan 0.1.0 → 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.
@@ -21,10 +21,23 @@ var GameFloorplan = (() => {
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ blocksWindow: () => blocksWindow,
24
25
  checkLot: () => checkLot,
26
+ createChalet: () => createChalet,
27
+ createCityBuilding: () => createCityBuilding,
28
+ createColonialHouse: () => createColonialHouse,
29
+ createCompanyHouse: () => createCompanyHouse,
30
+ createDetachedHouse: () => createDetachedHouse,
31
+ createFarmhouse: () => createFarmhouse,
25
32
  createRng: () => createRng,
33
+ createSilhouetteRandom: () => createSilhouetteRandom,
34
+ createSlabHouse: () => createSlabHouse,
35
+ createStiltHouse: () => createStiltHouse,
36
+ createTwoStoreyHouse: () => createTwoStoreyHouse,
26
37
  defineZone: () => defineZone,
27
38
  engine: () => engine,
39
+ findsAnswer: () => findsAnswer,
40
+ findsFor: () => findsFor,
28
41
  generate: () => generate,
29
42
  getZone: () => getZone,
30
43
  hashString: () => hashString,
@@ -38,6 +51,7 @@ var GameFloorplan = (() => {
38
51
  overlaps: () => overlaps,
39
52
  palette: () => palette,
40
53
  registerDecorProfile: () => registerDecorProfile,
54
+ registerFindsProvider: () => registerFindsProvider,
41
55
  registerGroundOverlay: () => registerGroundOverlay,
42
56
  registerItem: () => registerItem,
43
57
  registerItems: () => registerItems,
@@ -47,11 +61,13 @@ var GameFloorplan = (() => {
47
61
  registerPaletteStyle: () => registerPaletteStyle,
48
62
  registerPass: () => registerPass,
49
63
  registerPermeableGround: () => registerPermeableGround,
64
+ registerResultExtension: () => registerResultExtension,
50
65
  registerRoomColors: () => registerRoomColors,
51
66
  registerSeedKeys: () => registerSeedKeys,
52
67
  registerState: () => registerState,
53
68
  registerStyle: () => registerStyle,
54
69
  registerTemplate: () => registerTemplate,
70
+ registerViewerLayer: () => registerViewerLayer,
55
71
  registerWallTop: () => registerWallTop,
56
72
  setStateDistribution: () => setStateDistribution,
57
73
  slotKit: () => slotKit,
@@ -68,6 +84,227 @@ var GameFloorplan = (() => {
68
84
  var EPS = 1e-6;
69
85
  var TOL = 1e-3;
70
86
  var DOOR_CLEARANCE = 0.9;
87
+ var PLAYER_RADIUS = 0.24;
88
+ var DOOR_EDGE_CLEARANCE = 0.12;
89
+ var WINDOW_EDGE_CLEARANCE = 0.3;
90
+ var OVERHEAD_TOP = 2.15;
91
+ var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
92
+ "stockroom",
93
+ "kitchen",
94
+ "laundry",
95
+ "bathroom",
96
+ "void",
97
+ "elevator",
98
+ "stairs",
99
+ "pantry",
100
+ "closet",
101
+ "corridor",
102
+ "hall",
103
+ "balcony",
104
+ "terrace",
105
+ "garage",
106
+ "storage",
107
+ "porch",
108
+ "tent",
109
+ "outhouse"
110
+ ]);
111
+ var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
112
+
113
+ // src/registry.ts
114
+ var CATALOG = {};
115
+ var LIBRARIES = {};
116
+ var LIBRARY_ALIAS = {};
117
+ var TEMPLATES = {};
118
+ var STYLES = {};
119
+ var STYLE_ALIAS = {};
120
+ var PALETTE_STYLES = {};
121
+ var ROOM_COLORS = [];
122
+ var PASSES = [];
123
+ var DECOR_PROFILES = {};
124
+ var GROUND_OVERLAYS = [];
125
+ var STATE_PLUGINS = { normal: { apply: () => [] } };
126
+ var WALL_TOP_HEIGHTS = { none: 0 };
127
+ var SEED_KEYS = { rooms: {}, items: {} };
128
+ var stateDistribution = { weights: null, seedKey: "state" };
129
+ function registerItem(def) {
130
+ const h = def.h;
131
+ const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
132
+ CATALOG[def.type] = item;
133
+ return item;
134
+ }
135
+ function registerItems(list) {
136
+ for (const d of list) registerItem(d);
137
+ }
138
+ function registerLibrary(id, library) {
139
+ LIBRARIES[id] = library;
140
+ }
141
+ function registerLibraryAlias(alias, id) {
142
+ LIBRARY_ALIAS[alias] = id;
143
+ }
144
+ function registerTemplate(t) {
145
+ TEMPLATES[t.id] = t;
146
+ return t;
147
+ }
148
+ function registerStyle(style) {
149
+ STYLES[style.id] = style;
150
+ for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
151
+ }
152
+ function registerPaletteStyle(id, def) {
153
+ PALETTE_STYLES[id] = def;
154
+ }
155
+ function registerRoomColors(list) {
156
+ for (const d of list) {
157
+ const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
158
+ if (i >= 0) ROOM_COLORS.splice(i, 1);
159
+ ROOM_COLORS.push(d);
160
+ }
161
+ ROOM_COLORS.sort((a, b) => a.order - b.order);
162
+ }
163
+ function registerDecorProfile(styleId, profile) {
164
+ DECOR_PROFILES[styleId] = profile;
165
+ }
166
+ function registerPass(name, run) {
167
+ const i = PASSES.findIndex((p) => p.name === name);
168
+ if (i >= 0) PASSES[i] = { name, run };
169
+ else PASSES.push({ name, run });
170
+ }
171
+ function registerGroundOverlay(rule) {
172
+ GROUND_OVERLAYS.push(rule);
173
+ }
174
+ function registerWallTop(id, height) {
175
+ WALL_TOP_HEIGHTS[id] = height;
176
+ }
177
+ function registerSeedKeys(keys) {
178
+ Object.assign(SEED_KEYS.rooms, keys.rooms);
179
+ Object.assign(SEED_KEYS.items, keys.items);
180
+ }
181
+ function registerState(name, plugin) {
182
+ STATE_PLUGINS[name] = plugin;
183
+ }
184
+ function setStateDistribution(weights, seedKey = "state") {
185
+ stateDistribution.weights = weights;
186
+ stateDistribution.seedKey = seedKey;
187
+ }
188
+ var listStates = () => Object.keys(STATE_PLUGINS);
189
+ function resolveStyleId(id) {
190
+ const s = id ?? "";
191
+ return STYLE_ALIAS[s] ?? s;
192
+ }
193
+ function styleOf(id) {
194
+ return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
195
+ }
196
+ var RESULT_EXTENSIONS = [];
197
+ var FINDS_PROVIDERS = [];
198
+ function registerResultExtension(name, run, options = {}) {
199
+ const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
200
+ const i = RESULT_EXTENSIONS.findIndex((x) => x.name === name);
201
+ if (i >= 0) RESULT_EXTENSIONS[i] = entry;
202
+ else RESULT_EXTENSIONS.push(entry);
203
+ }
204
+ function registerFindsProvider(provider) {
205
+ FINDS_PROVIDERS.push(provider);
206
+ }
207
+ var VIEWER_LAYERS = [];
208
+ function registerViewerLayer(layer) {
209
+ const i = VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
210
+ if (i >= 0) VIEWER_LAYERS[i] = layer;
211
+ else VIEWER_LAYERS.push(layer);
212
+ }
213
+
214
+ // src/rng.ts
215
+ function mulberry32(a) {
216
+ return function() {
217
+ a |= 0;
218
+ a = a + 1831565813 | 0;
219
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
220
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
221
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
222
+ };
223
+ }
224
+ function hashString(input) {
225
+ const s = String(input);
226
+ let h = 1779033703 ^ s.length;
227
+ for (let i = 0; i < s.length; i++) {
228
+ h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
229
+ h = h << 13 | h >>> 19;
230
+ }
231
+ h = Math.imul(h ^ h >>> 16, 2246822507);
232
+ h = Math.imul(h ^ h >>> 13, 3266489909);
233
+ return (h ^ h >>> 16) >>> 0;
234
+ }
235
+ var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
236
+ function pickWeighted(rng, weights) {
237
+ const keys = Object.keys(weights);
238
+ let total = 0;
239
+ for (const k of keys) total += weights[k];
240
+ let x = rng() * total;
241
+ for (const k of keys) {
242
+ x -= weights[k];
243
+ if (x < 0) return k;
244
+ }
245
+ return keys[keys.length - 1];
246
+ }
247
+ function weightedOrder(candidates, rng) {
248
+ const arr = candidates.slice();
249
+ const out = [];
250
+ while (arr.length) {
251
+ let total = 0;
252
+ for (const c of arr) total += c.weight;
253
+ let x = rng() * total;
254
+ let i = 0;
255
+ for (; i < arr.length - 1; i++) {
256
+ x -= arr[i].weight;
257
+ if (x < 0) break;
258
+ }
259
+ out.push(arr.splice(i, 1)[0]);
260
+ }
261
+ return out;
262
+ }
263
+ function shuffle(arr, rng) {
264
+ const a = arr.slice();
265
+ for (let i = a.length - 1; i > 0; i--) {
266
+ const j = Math.floor(rng() * (i + 1));
267
+ [a[i], a[j]] = [a[j], a[i]];
268
+ }
269
+ return a;
270
+ }
271
+
272
+ // src/extensions.ts
273
+ function orderOf(extensions) {
274
+ const ordered = [];
275
+ const visiting = /* @__PURE__ */ new Set();
276
+ const visit = (extension) => {
277
+ if (ordered.includes(extension)) return;
278
+ if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
279
+ visiting.add(extension.name);
280
+ for (const name of extension.after) {
281
+ const before = extensions.find((x) => x.name === name);
282
+ if (before) visit(before);
283
+ }
284
+ visiting.delete(extension.name);
285
+ ordered.push(extension);
286
+ };
287
+ extensions.forEach(visit);
288
+ return ordered;
289
+ }
290
+ function runResultExtensions(plan, result, seed, options) {
291
+ for (const extension of orderOf(RESULT_EXTENSIONS)) {
292
+ result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
293
+ }
294
+ }
295
+ function findsFor(query) {
296
+ return answerOf(query) ?? [];
297
+ }
298
+ function findsAnswer(query) {
299
+ return answerOf(query);
300
+ }
301
+ function answerOf(query) {
302
+ for (const provider of FINDS_PROVIDERS) {
303
+ const stacks = provider.findsFor(query);
304
+ if (stacks !== null) return stacks;
305
+ }
306
+ return null;
307
+ }
71
308
 
72
309
  // src/geometry.ts
73
310
  var r2 = (x) => Math.round(x * 100) / 100;
@@ -170,13 +407,39 @@ var GameFloorplan = (() => {
170
407
  };
171
408
  }
172
409
  function usableParts(room, extra) {
173
- return room.parts.map((p) => {
174
- const isOpen = (k) => room.parts.some(
175
- (q) => q !== p && (k === 0 ? eq(q[2], p[0]) && Math.min(q[3], p[3]) - Math.max(q[1], p[1]) > 0.3 : k === 2 ? eq(q[0], p[2]) && Math.min(q[3], p[3]) - Math.max(q[1], p[1]) > 0.3 : k === 1 ? eq(q[3], p[1]) && Math.min(q[2], p[2]) - Math.max(q[0], p[0]) > 0.3 : eq(q[1], p[3]) && Math.min(q[2], p[2]) - Math.max(q[0], p[0]) > 0.3)
176
- );
177
- const m = MARGIN + (extra || 0);
178
- return [p[0] + (isOpen(0) ? 0 : m), p[1] + (isOpen(1) ? 0 : m), p[2] - (isOpen(2) ? 0 : m), p[3] - (isOpen(3) ? 0 : m)];
179
- });
410
+ const m = MARGIN + (extra || 0);
411
+ const out = [];
412
+ for (const p of room.parts) {
413
+ out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
414
+ for (let k = 0; k < 4; k++) {
415
+ const spans = [];
416
+ for (const q of room.parts) {
417
+ if (q === p) continue;
418
+ if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
419
+ else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
420
+ else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
421
+ else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
422
+ }
423
+ spans.sort((x, y) => x[0] - y[0]);
424
+ const merged = [];
425
+ for (const span of spans) {
426
+ const last = merged[merged.length - 1];
427
+ if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
428
+ else merged.push([span[0], span[1]]);
429
+ }
430
+ for (const [a, b] of merged) {
431
+ if (b - a <= 0.3) continue;
432
+ const x0 = a + m;
433
+ const x1 = b - m;
434
+ if (x1 <= x0) continue;
435
+ if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
436
+ else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
437
+ else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
438
+ else out.push([x0, p[3] - m, x1, p[3]]);
439
+ }
440
+ }
441
+ }
442
+ return out;
180
443
  }
181
444
  var inRect = (room, u, v) => room.parts.some((p) => u > p[0] + EPS && u < p[2] - EPS && v > p[1] + EPS && v < p[3] - EPS);
182
445
  function buildWalls(rooms, doors, openPairs, floorIndex) {
@@ -231,7 +494,7 @@ var GameFloorplan = (() => {
231
494
  pieces = n;
232
495
  }
233
496
  for (const [x, y] of pieces) {
234
- const isOpen = (t) => t === "balcony" || t === "terrace";
497
+ const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
235
498
  const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
236
499
  if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
237
500
  const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
@@ -254,6 +517,39 @@ var GameFloorplan = (() => {
254
517
  return out;
255
518
  }
256
519
 
520
+ // src/parking.ts
521
+ var STEP = 0.05;
522
+ var MAX_CANDIDATES = 80;
523
+ function carCandidates(slot, item, car) {
524
+ const front = [slot.frontU, slot.frontV];
525
+ const zone = slot._cell.R;
526
+ const alongV = Math.abs(front[1]) === 1;
527
+ const [extentU, extentV] = extent([item.w, item.d], front);
528
+ const across = alongV ? extentU : extentV;
529
+ const along = alongV ? extentV : extentU;
530
+ const a = alongV ? 0 : 1;
531
+ const b = alongV ? 1 : 0;
532
+ const driverSign = alongV ? front[1] : -front[0];
533
+ const frontSign = alongV ? front[1] : front[0];
534
+ const lowClear = driverSign < 0 ? car.driver : car.side;
535
+ const highClear = driverSign < 0 ? car.side : car.driver;
536
+ const lo = zone[a] + lowClear;
537
+ const hi = zone[a + 2] - highClear - across;
538
+ const alongLo = zone[b] + car.end;
539
+ const alongHi = zone[b + 2] - car.end - along;
540
+ if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
541
+ const preferred = (alongV ? slot.x : slot.z) - across / 2;
542
+ const alongStart = frontSign < 0 ? alongHi : alongLo;
543
+ const positions = [];
544
+ for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
545
+ positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
546
+ return positions.slice(0, MAX_CANDIDATES).map((p) => {
547
+ const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
548
+ const clear = alongV ? [p - lowClear, alongStart - car.end, p + across + highClear, alongStart + along + car.end] : [alongStart - car.end, p - lowClear, alongStart + along + car.end, p + across + highClear];
549
+ return { rect, clear };
550
+ });
551
+ }
552
+
257
553
  // src/queries.ts
258
554
  function zonesOfFloor(plan, floorIndex) {
259
555
  const out = [];
@@ -263,6 +559,7 @@ var GameFloorplan = (() => {
263
559
  for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
264
560
  for (const g of plan.gates) out.push(...g.zones);
265
561
  for (const c of plan.paths) out.push(...c.rects);
562
+ out.push(...plan.reservations);
266
563
  }
267
564
  return out;
268
565
  }
@@ -284,7 +581,10 @@ var GameFloorplan = (() => {
284
581
  if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
285
582
  return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
286
583
  }
287
- var blocksWindow = (it, win) => !it.window || it.h > 1 + EPS || it.h > win.y0 - 0.05;
584
+ function blocksWindow(it, win, overheadOff) {
585
+ const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
586
+ return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
587
+ }
288
588
  function windowsOfRoom(plan, room) {
289
589
  if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
290
590
  return plan.windows.filter(
@@ -309,8 +609,22 @@ var GameFloorplan = (() => {
309
609
  }
310
610
 
311
611
  // src/grid.ts
312
- function createGrid(plan, room, ignored) {
313
- const gs = room.outdoor ? 0.3 : 0.2;
612
+ function wallBodies(plan, floor) {
613
+ const cache = plan.wallBodies = plan.wallBodies || {};
614
+ if (cache[floor]) return cache[floor];
615
+ const doors = plan.doors.filter((p) => p.floor === floor);
616
+ return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
617
+ const a0 = p.axis === "u" ? p.u0 : p.v0;
618
+ const a1 = p.axis === "u" ? p.u1 : p.v1;
619
+ const pos = p.axis === "u" ? p.v0 : p.u0;
620
+ const atDoor = (x) => doors.some((q) => q.axis === p.axis && eq(q.pos, pos) && (eq(q.at - q.width / 2, x) || eq(q.at + q.width / 2, x)));
621
+ const e0 = atDoor(a0) ? 0 : MARGIN;
622
+ const e1 = atDoor(a1) ? 0 : MARGIN;
623
+ return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
624
+ });
625
+ }
626
+ function createGrid(plan, room, ignored, radius = plan.playerRadius) {
627
+ const gs = room.outdoor ? 0.3 : 0.1;
314
628
  let u0 = 1e9;
315
629
  let v0 = 1e9;
316
630
  let u1 = -1e9;
@@ -324,12 +638,18 @@ var GameFloorplan = (() => {
324
638
  const nx = Math.ceil((u1 - u0) / gs);
325
639
  const nz = Math.ceil((v1 - v0) / gs);
326
640
  const insideCells = new Uint8Array(nx * nz);
327
- const insets = usableParts(room, 0.08);
641
+ const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
642
+ const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
643
+ const tooClose = (u, v) => bodies.some((r) => {
644
+ const dx = Math.max(r[0] - u, 0, u - r[2]);
645
+ const dz = Math.max(r[1] - v, 0, v - r[3]);
646
+ return dx * dx + dz * dz < radius * radius - 1e-9;
647
+ });
328
648
  for (let j = 0; j < nz; j++)
329
649
  for (let i = 0; i < nx; i++) {
330
650
  const u = u0 + (i + 0.5) * gs;
331
651
  const v = v0 + (j + 0.5) * gs;
332
- if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3])) insideCells[j * nx + i] = 1;
652
+ if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3]) && (room.outdoor || !tooClose(u, v))) insideCells[j * nx + i] = 1;
333
653
  }
334
654
  const entries = [];
335
655
  for (const ab of openingsOfFloor(plan, room.floor))
@@ -340,6 +660,22 @@ var GameFloorplan = (() => {
340
660
  const j = Math.floor((pt[1] - v0) / gs);
341
661
  if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
342
662
  }
663
+ if (!room.outdoor)
664
+ for (const pair of plan.openPairs[room.floor] || []) {
665
+ const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
666
+ const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
667
+ if (!other) continue;
668
+ const edge = sharedEdge(room, other, null);
669
+ if (!edge || edge.a1 - edge.a0 > 2) continue;
670
+ const mid = (edge.a0 + edge.a1) / 2;
671
+ for (const offset of [-0.4, 0.4]) {
672
+ const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
673
+ if (!inRect(room, pt[0], pt[1])) continue;
674
+ const i = Math.floor((pt[0] - u0) / gs);
675
+ const j = Math.floor((pt[1] - v0) / gs);
676
+ if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
677
+ }
678
+ }
343
679
  const R = inner(room.parts[0]);
344
680
  return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
345
681
  }
@@ -399,154 +735,12 @@ var GameFloorplan = (() => {
399
735
  return true;
400
736
  }
401
737
 
402
- // src/registry.ts
403
- var CATALOG = {};
404
- var LIBRARIES = {};
405
- var LIBRARY_ALIAS = {};
406
- var TEMPLATES = {};
407
- var STYLES = {};
408
- var STYLE_ALIAS = {};
409
- var PALETTE_STYLES = {};
410
- var ROOM_COLORS = [];
411
- var PASSES = [];
412
- var DECOR_PROFILES = {};
413
- var GROUND_OVERLAYS = [];
414
- var STATE_PLUGINS = { normal: { apply: () => [] } };
415
- var WALL_TOP_HEIGHTS = { none: 0 };
416
- var SEED_KEYS = { rooms: {}, items: {} };
417
- var stateDistribution = { weights: null, seedKey: "state" };
418
- function registerItem(def) {
419
- const h = def.h;
420
- const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
421
- CATALOG[def.type] = item;
422
- return item;
423
- }
424
- function registerItems(list) {
425
- for (const d of list) registerItem(d);
426
- }
427
- function registerLibrary(id, library) {
428
- LIBRARIES[id] = library;
429
- }
430
- function registerLibraryAlias(alias, id) {
431
- LIBRARY_ALIAS[alias] = id;
432
- }
433
- function registerTemplate(t) {
434
- TEMPLATES[t.id] = t;
435
- return t;
436
- }
437
- function registerStyle(style) {
438
- STYLES[style.id] = style;
439
- for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
440
- }
441
- function registerPaletteStyle(id, def) {
442
- PALETTE_STYLES[id] = def;
443
- }
444
- function registerRoomColors(list) {
445
- for (const d of list) {
446
- const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
447
- if (i >= 0) ROOM_COLORS.splice(i, 1);
448
- ROOM_COLORS.push(d);
449
- }
450
- ROOM_COLORS.sort((a, b) => a.order - b.order);
451
- }
452
- function registerDecorProfile(styleId, profile) {
453
- DECOR_PROFILES[styleId] = profile;
454
- }
455
- function registerPass(name, run) {
456
- const i = PASSES.findIndex((p) => p.name === name);
457
- if (i >= 0) PASSES[i] = { name, run };
458
- else PASSES.push({ name, run });
459
- }
460
- function registerGroundOverlay(rule) {
461
- GROUND_OVERLAYS.push(rule);
462
- }
463
- function registerWallTop(id, height) {
464
- WALL_TOP_HEIGHTS[id] = height;
465
- }
466
- function registerSeedKeys(keys) {
467
- Object.assign(SEED_KEYS.rooms, keys.rooms);
468
- Object.assign(SEED_KEYS.items, keys.items);
469
- }
470
- function registerState(name, plugin) {
471
- STATE_PLUGINS[name] = plugin;
472
- }
473
- function setStateDistribution(weights, seedKey = "state") {
474
- stateDistribution.weights = weights;
475
- stateDistribution.seedKey = seedKey;
476
- }
477
- var listStates = () => Object.keys(STATE_PLUGINS);
478
- function resolveStyleId(id) {
479
- const s = id ?? "";
480
- return STYLE_ALIAS[s] ?? s;
481
- }
482
- function styleOf(id) {
483
- return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
484
- }
485
-
486
- // src/rng.ts
487
- function mulberry32(a) {
488
- return function() {
489
- a |= 0;
490
- a = a + 1831565813 | 0;
491
- let t = Math.imul(a ^ a >>> 15, 1 | a);
492
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
493
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
494
- };
495
- }
496
- function hashString(input) {
497
- const s = String(input);
498
- let h = 1779033703 ^ s.length;
499
- for (let i = 0; i < s.length; i++) {
500
- h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
501
- h = h << 13 | h >>> 19;
502
- }
503
- h = Math.imul(h ^ h >>> 16, 2246822507);
504
- h = Math.imul(h ^ h >>> 13, 3266489909);
505
- return (h ^ h >>> 16) >>> 0;
506
- }
507
- var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
508
- function pickWeighted(rng, weights) {
509
- const keys = Object.keys(weights);
510
- let total = 0;
511
- for (const k of keys) total += weights[k];
512
- let x = rng() * total;
513
- for (const k of keys) {
514
- x -= weights[k];
515
- if (x < 0) return k;
516
- }
517
- return keys[keys.length - 1];
518
- }
519
- function weightedOrder(candidates, rng) {
520
- const arr = candidates.slice();
521
- const out = [];
522
- while (arr.length) {
523
- let total = 0;
524
- for (const c of arr) total += c.weight;
525
- let x = rng() * total;
526
- let i = 0;
527
- for (; i < arr.length - 1; i++) {
528
- x -= arr[i].weight;
529
- if (x < 0) break;
530
- }
531
- out.push(arr.splice(i, 1)[0]);
532
- }
533
- return out;
534
- }
535
- function shuffle(arr, rng) {
536
- const a = arr.slice();
537
- for (let i = a.length - 1; i > 0; i--) {
538
- const j = Math.floor(rng() * (i + 1));
539
- [a[i], a[j]] = [a[j], a[i]];
540
- }
541
- return a;
542
- }
543
-
544
738
  // src/fill.ts
545
739
  function candidatesOf(cats) {
546
740
  const out = [];
547
741
  for (const [cat, weight] of Object.entries(cats)) {
548
742
  const exact = cat[0] === "=";
549
- const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : i.category === cat || i.type === cat));
743
+ const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
550
744
  for (const it of its) out.push({ it, weight: weight / its.length });
551
745
  }
552
746
  return out;
@@ -570,6 +764,7 @@ var GameFloorplan = (() => {
570
764
  }
571
765
  return [u0, v0, u0 + extentU, v0 + extentV];
572
766
  }
767
+ var RELAXED_RADIUS = 0.12;
573
768
  var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
574
769
  function rotationFor(f, front) {
575
770
  const meshFront = MESH_FRONT[front] || [0, 1];
@@ -587,9 +782,10 @@ var GameFloorplan = (() => {
587
782
  const rims = [];
588
783
  const inners = usableParts(room, 0);
589
784
  const grid = createGrid(plan, room);
785
+ const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
590
786
  const zones = zonesOfFloor(plan, room.floor);
591
787
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
592
- const inflation = room.outdoor ? 0.15 : 0.12;
788
+ const inflation = room.outdoor ? 0.15 : plan.playerRadius;
593
789
  const units = [];
594
790
  const seen = {};
595
791
  for (const s of slots2) {
@@ -608,7 +804,7 @@ var GameFloorplan = (() => {
608
804
  const dependencies = (u) => u.members.flatMap((m) => [m.requires, m.on]).filter(Boolean).filter((id) => slots2.some((s) => s.id === id && !u.members.includes(s)));
609
805
  const done = /* @__PURE__ */ new Set();
610
806
  const ready = (u) => dependencies(u).every((id) => done.has(id));
611
- const make = (s, it, r, parent) => {
807
+ const make = (s, it, r, parent, overheadOff) => {
612
808
  const f = [s.frontU, s.frontV];
613
809
  const id = room.id + "." + s.id;
614
810
  const m = {
@@ -637,13 +833,18 @@ var GameFloorplan = (() => {
637
833
  _r: r,
638
834
  _seedId: SEED_KEYS.items[id] ?? id
639
835
  };
836
+ if (it.overhead) m.overhead = !overheadOff;
837
+ if (it.category === "car") {
838
+ m.driver = [f[1], -f[0]];
839
+ m.carClearance = plan.carClearance;
840
+ }
640
841
  if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
641
842
  if (room.link) m.link = { from: room.link[0], to: room.link[1] };
642
843
  state[s.id] = m;
643
844
  items.push(m);
644
845
  return m;
645
846
  };
646
- const tryFit = (s, relax) => {
847
+ const tryFit = (s, relax, overheadOff) => {
647
848
  const cands = weightedOrder(candidatesOf(s.cats), rng);
648
849
  if (s.anchor === "on" || s.anchor === "inside") {
649
850
  const parent = state[s.on];
@@ -666,19 +867,22 @@ var GameFloorplan = (() => {
666
867
  const shifts = [0];
667
868
  for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
668
869
  let found = null;
669
- for (const shift of shifts) {
670
- const r3 = itemRect(s, it, shift);
870
+ const car = it.category === "car" && !relax;
871
+ const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
872
+ for (const attempt of tries) {
873
+ const r3 = attempt.rect;
671
874
  if (!insideUnion(r3, inners)) continue;
875
+ if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
672
876
  if (it.flat) {
673
877
  found = { rect: r3 };
674
878
  break;
675
879
  }
676
880
  if (zones.some((z) => overlaps(r3, z))) continue;
677
- if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r3, z))) continue;
881
+ if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
678
882
  if (solids.some((o) => overlaps(r3, o))) continue;
679
883
  if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
680
- let cl2 = null;
681
- if (!relax && s.clearance > 0 && !it.low) {
884
+ let cl2 = car ? attempt.clear : null;
885
+ if (!car && !relax && s.clearance > 0 && !it.low) {
682
886
  cl2 = clearRect(r3, f, s.clearance);
683
887
  if (!insideUnion(cl2, inners)) continue;
684
888
  if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
@@ -689,14 +893,14 @@ var GameFloorplan = (() => {
689
893
  rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
690
894
  if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
691
895
  }
692
- if (!reachable(grid, solids.concat([r3]), inflation)) continue;
896
+ if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
693
897
  found = { rect: r3, cl: cl2, rimRect: rimRect2 };
694
898
  break;
695
899
  }
696
900
  if (!found) continue;
697
901
  const { rect: r, cl, rimRect } = found;
698
902
  if (it.flat) return make(s, it, r, null);
699
- const m = make(s, it, r, null);
903
+ const m = make(s, it, r, null, overheadOff);
700
904
  if (cl) reserves.push(cl);
701
905
  if (rimRect) rims.push(rimRect);
702
906
  solids.push(r);
@@ -719,9 +923,16 @@ var GameFloorplan = (() => {
719
923
  break;
720
924
  }
721
925
  }
926
+ if (!ok)
927
+ for (const m of applicable) {
928
+ if (tryFit(m, false, true)) {
929
+ ok = true;
930
+ break;
931
+ }
932
+ }
722
933
  if (!ok && u.required)
723
934
  for (const m of applicable) {
724
- if (tryFit(m, true)) {
935
+ if (tryFit(m, true, true)) {
725
936
  ok = true;
726
937
  break;
727
938
  }
@@ -770,6 +981,8 @@ var GameFloorplan = (() => {
770
981
  createGrid,
771
982
  clearRect,
772
983
  pickWeighted,
984
+ createRng,
985
+ roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
773
986
  shuffle,
774
987
  facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
775
988
  cutIntervals,
@@ -833,6 +1046,10 @@ var GameFloorplan = (() => {
833
1046
  const zones = zonesOfFloor(plan, room.floor);
834
1047
  const occupied = itemsOfRoom(room);
835
1048
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
1049
+ const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
1050
+ const r = m.aabb;
1051
+ return m.frontV === 1 ? [r[0], r[3], r[2], r[3] + 1] : m.frontV === -1 ? [r[0], r[1] - 1, r[2], r[1]] : m.frontU === 1 ? [r[2], r[1], r[2] + 1, r[3]] : [r[0] - 1, r[1], r[0], r[3]];
1052
+ });
836
1053
  const cand = opts.cands ? opts.cands.slice() : [];
837
1054
  if (!opts.cands)
838
1055
  for (const p of inners) {
@@ -847,9 +1064,9 @@ var GameFloorplan = (() => {
847
1064
  cand.push([x, z, x + extentU, z + extentV]);
848
1065
  }
849
1066
  }
850
- for (const r of shuffle(cand, rng2)) {
1067
+ for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
851
1068
  if (!insideUnion(r, inners)) continue;
852
- if (zones.some((z) => overlaps(r, z))) continue;
1069
+ if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
853
1070
  if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
854
1071
  if (solidList.some((o) => overlaps(r, o))) continue;
855
1072
  if (opts.flat) {
@@ -858,7 +1075,7 @@ var GameFloorplan = (() => {
858
1075
  }
859
1076
  if (occupied.some((m) => !m.low && !m.flat && !m.on && !m.decor && m.clearance > 0 && overlaps(r, clearRect(m._r, [m.frontU, m.frontV], m.clearance)))) continue;
860
1077
  if (opts.without && opts.without(r)) continue;
861
- if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), 0.16)) continue;
1078
+ if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
862
1079
  return r;
863
1080
  }
864
1081
  return null;
@@ -939,6 +1156,129 @@ var GameFloorplan = (() => {
939
1156
  return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
940
1157
  }
941
1158
 
1159
+ // src/paths.ts
1160
+ var CLOSE = 1e-6;
1161
+ var rounded = (r) => r.map(r2);
1162
+ var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
1163
+ function addExteriorDoorPaths(plan, c) {
1164
+ const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
1165
+ const social = gates.find((g) => g.type === "pedestrian");
1166
+ const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
1167
+ const parkingZone = zones.find((z) => z.id === "parking");
1168
+ const parking = parkingZone ? parkingZone.parts[0] : null;
1169
+ const midY = setback > 0 ? -setback / 2 : 0;
1170
+ const joinToSocial = (x, width, rects, points) => {
1171
+ if (!socialPath) return;
1172
+ const h = width / 2;
1173
+ const horizontal = socialPath.rects.find((r) => midY >= r[1] - CLOSE && midY <= r[3] + CLOSE && r[2] - r[0] > r[3] - r[1]) || socialPath.rects[0];
1174
+ const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
1175
+ if (Math.abs(targetX - x) > CLOSE) {
1176
+ rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
1177
+ points.push([targetX, midY]);
1178
+ }
1179
+ };
1180
+ for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
1181
+ if (paths.some((p) => p.to === door.id)) continue;
1182
+ const half = Math.max(1, Math.min(door.width, 2)) / 2;
1183
+ const rects = [];
1184
+ const points = [];
1185
+ const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
1186
+ let fromId = social ? social.id : null;
1187
+ let width = 1;
1188
+ const id = isTarget ? "entrance_path" : "path_" + door.id;
1189
+ if (door.side === "front") {
1190
+ if (setback > 0) {
1191
+ const stub = Math.min(1, setback);
1192
+ rects.push([door.u - half, -stub, door.u + half, 0]);
1193
+ points.push([door.u, 0], [door.u, -stub]);
1194
+ if (-stub > midY) {
1195
+ rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
1196
+ points.push([door.u, midY]);
1197
+ }
1198
+ joinToSocial(door.u, width, rects, points);
1199
+ } else {
1200
+ rects.push([door.u - half, -1.2, door.u + half, 0]);
1201
+ points.push([door.u, 0], [door.u, -1.2]);
1202
+ }
1203
+ } else {
1204
+ const lanes = [];
1205
+ if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
1206
+ if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
1207
+ const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1208
+ let best = null;
1209
+ for (const lane of lanes) {
1210
+ if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
1211
+ const laneWidth = Math.min(0.9, lane.width - 0.1);
1212
+ const h = laneWidth / 2;
1213
+ const rearStrip = Math.min(0.8, backyard);
1214
+ const rearY = door.v + rearStrip + laneWidth / 2;
1215
+ const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
1216
+ const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
1217
+ let frontStub = null;
1218
+ if (!overlapsParking && setback > 0) {
1219
+ const dir = social && social.u < lane.x ? -1 : 1;
1220
+ const band = [-0.6 - h, -0.6 + h];
1221
+ let nearestX = social ? social.u : lane.x;
1222
+ let nearestGap = Infinity;
1223
+ for (const r of targets) {
1224
+ if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
1225
+ const edge = dir < 0 ? r[2] : r[0];
1226
+ const gap = Math.abs(edge - lane.x);
1227
+ if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
1228
+ nearestGap = gap;
1229
+ nearestX = edge;
1230
+ }
1231
+ }
1232
+ frontStub = [Math.min(lane.x, nearestX) - (dir < 0 ? 0 : h), -0.6 - h, Math.max(lane.x, nearestX) + (dir < 0 ? h : 0), -0.6 + h];
1233
+ }
1234
+ const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
1235
+ const horizontal = door.side === "back" ? [Math.min(door.u, lane.x) - half, rearY - laneWidth / 2, Math.max(door.u, lane.x) + h, rearY + laneWidth / 2] : null;
1236
+ const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
1237
+ if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
1238
+ }
1239
+ if (best) {
1240
+ const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
1241
+ width = laneWidth;
1242
+ if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
1243
+ if (door.side === "back") {
1244
+ rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
1245
+ points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
1246
+ } else {
1247
+ const x0 = door.side === "left" ? lane.x - h : w;
1248
+ const x1 = door.side === "left" ? 0 : lane.x + h;
1249
+ rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
1250
+ points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
1251
+ }
1252
+ if (frontStub) {
1253
+ rects.push(frontStub);
1254
+ points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
1255
+ }
1256
+ } else {
1257
+ const stub = 1;
1258
+ if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
1259
+ else rects.push(door.side === "left" ? [-Math.min(stub, marginLeft), door.v - half, 0, door.v + half] : [w, door.v - half, w + Math.min(stub, marginRight), door.v + half]);
1260
+ points.push([door.u, door.v]);
1261
+ }
1262
+ }
1263
+ const reached = rects.slice();
1264
+ const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1265
+ let grows = true;
1266
+ while (grows) {
1267
+ grows = false;
1268
+ for (const r of pool) {
1269
+ if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
1270
+ reached.push(r);
1271
+ grows = true;
1272
+ }
1273
+ }
1274
+ }
1275
+ const inside = (g) => reached.some((r) => g.u >= r[0] - CLOSE && g.u <= r[2] + CLOSE && g.v >= r[1] - CLOSE && g.v <= r[3] + CLOSE);
1276
+ const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
1277
+ if (gate) fromId = gate.id;
1278
+ paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
1279
+ }
1280
+ }
1281
+
942
1282
  // src/zoning.ts
943
1283
  var ZONES = {};
944
1284
  var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
@@ -1070,14 +1410,34 @@ var GameFloorplan = (() => {
1070
1410
  const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
1071
1411
  const FLOOR_DEFS = floorsOf(tpl, op);
1072
1412
  const style = styleOf(styleId);
1073
- const plan = { w, d, tpl, floorDefs: FLOOR_DEFS, style: styleId, floors: [], rooms: [], zones: [], doors: [], windows: [], walls: [], gates: [], paths: [], lot: null, openPairs: {} };
1413
+ const plan = {
1414
+ w,
1415
+ d,
1416
+ tpl,
1417
+ floorDefs: FLOOR_DEFS,
1418
+ style: styleId,
1419
+ floors: [],
1420
+ rooms: [],
1421
+ zones: [],
1422
+ doors: [],
1423
+ windows: [],
1424
+ walls: [],
1425
+ gates: [],
1426
+ paths: [],
1427
+ lot: null,
1428
+ openPairs: {},
1429
+ reservations: [],
1430
+ options: op,
1431
+ playerRadius: op.playerRadius ?? PLAYER_RADIUS,
1432
+ carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
1433
+ };
1074
1434
  FLOOR_DEFS.forEach((A, floorIndex) => {
1075
1435
  const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
1076
1436
  plan.floors.push({
1077
1437
  index: floorIndex,
1078
1438
  name: tpl.singleFloor ? "floor_n" : A.label,
1079
1439
  number: tpl.singleFloor ? floorNumber : void 0,
1080
- y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : floorIndex * A.ceiling,
1440
+ y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
1081
1441
  ceiling: A.ceiling
1082
1442
  });
1083
1443
  plan.openPairs[floorIndex] = A.openPairs || [];
@@ -1093,7 +1453,8 @@ var GameFloorplan = (() => {
1093
1453
  const a = byId[sp.a];
1094
1454
  if (!a) throw new Error("Door references a missing room: " + sp.a);
1095
1455
  let edge;
1096
- if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1456
+ if (sp.edge) edge = sp.edge;
1457
+ else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1097
1458
  else {
1098
1459
  const b = byId[sp.b];
1099
1460
  if (!b) throw new Error("Door references a missing room: " + sp.b);
@@ -1101,7 +1462,7 @@ var GameFloorplan = (() => {
1101
1462
  }
1102
1463
  if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
1103
1464
  const width = sp.width;
1104
- const margin = width / 2 + 0.12;
1465
+ const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
1105
1466
  if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
1106
1467
  const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
1107
1468
  const exterior = sp.b === "outside";
@@ -1123,51 +1484,101 @@ var GameFloorplan = (() => {
1123
1484
  exterior,
1124
1485
  side: sp.side || null,
1125
1486
  leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
1126
- model: sp.steel ? "steel" : width > 1.1 ? "double" : "single",
1487
+ model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
1488
+ type: sp.type || "door",
1489
+ steps: sp.steps || 0,
1127
1490
  hinge: "start",
1128
1491
  zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
1129
1492
  });
1130
1493
  });
1131
1494
  plan.doors.push(...floorDoors);
1132
- const windowsOut = [];
1133
- (A.windows(w, d, op) || []).forEach((windowDef) => {
1134
- const a = byId[windowDef.room];
1135
- if (!a) throw new Error("Window references a missing room: " + windowDef.room);
1136
- const edge = exteriorEdge(a, windowDef.side, w, d, windowDef.at);
1137
- if (!edge) return;
1138
- const width = windowDef.width;
1139
- const m0 = edge.a0 + width / 2 + 0.3;
1140
- const m1 = edge.a1 - width / 2 - 0.3;
1141
- if (m1 < m0 - TOL) return;
1142
- const centers = windowDef.n ? Array.from({ length: windowDef.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (windowDef.n + 1)) : [windowDef.at];
1143
- for (const c0 of centers) {
1144
- const c = clamp(c0, m0, m1);
1145
- const u = edge.axis === "u" ? c : edge.pos;
1146
- const v = edge.axis === "u" ? edge.pos : c;
1147
- if (floorDoors.some((p) => p.axis === edge.axis && eq(p.pos, edge.pos) && Math.abs(p.at - c) < (p.width + width) / 2 + 0.15)) continue;
1148
- if (windowsOut.some((j) => j.axis === edge.axis && eq(j.pos, edge.pos) && Math.abs(j.at - c) < (j.width + width) / 2 + 0.2)) continue;
1149
- windowsOut.push({
1150
- id: "w" + floorIndex + "_" + windowsOut.length,
1151
- floor: floorIndex,
1152
- u: r2(u),
1153
- v: r2(v),
1154
- axis: edge.axis,
1155
- pos: edge.pos,
1156
- at: r2(c),
1157
- width,
1158
- y0: windowDef.y0 || 1,
1159
- y1: windowDef.y1 || 2.15,
1160
- room: windowDef.room,
1161
- side: windowDef.side
1162
- });
1163
- }
1164
- });
1495
+ const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
1496
+ const windowsOut = placeWindows({ list, byId, floorWalls, windowDefs: A.windows(w, d, op) || [], floorIndex, ceiling: A.ceiling, w, d, clearance: tpl.clearance?.window ?? WINDOW_EDGE_CLEARANCE });
1165
1497
  plan.windows.push(...windowsOut);
1166
- plan.walls.push(...buildWalls(list, floorDoors, A.openPairs || [], floorIndex).map((p) => Object.assign(p, { h: p.h || A.ceiling })));
1498
+ plan.walls.push(...floorWalls);
1167
1499
  });
1500
+ if (tpl.config) plan.config = tpl.config(op, w, d);
1501
+ if (tpl.reservations) tpl.reservations(plan);
1168
1502
  buildLot(plan, tpl, w, d, op, style, styleId, bc);
1169
1503
  return plan;
1170
1504
  }
1505
+ function placeWindows(params) {
1506
+ const { list, byId, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
1507
+ const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
1508
+ const found = [];
1509
+ const groups = [];
1510
+ let order = 0;
1511
+ for (const def of windowDefs) {
1512
+ const room = byId[def.room];
1513
+ if (!room) throw new Error("Window references a missing room: " + def.room);
1514
+ const edge = exteriorEdge(room, def.side, w, d, def.at);
1515
+ if (!edge) continue;
1516
+ const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
1517
+ for (const centre of centres) {
1518
+ const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
1519
+ const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
1520
+ const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
1521
+ const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
1522
+ const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
1523
+ if (!target) continue;
1524
+ let group = groups.find((x) => x.wall === target && x.def.room === def.room);
1525
+ if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
1526
+ group.entries.push({ def, centre, order: order++ });
1527
+ }
1528
+ }
1529
+ for (const g of groups) {
1530
+ const room = byId[g.def.room];
1531
+ const bath = room.type === "bathroom";
1532
+ const length = g.end - g.start;
1533
+ const its = g.entries.slice().sort((x, y) => x.centre - y.centre).map((x) => ({ def: x.def, order: x.order, width: bath ? Math.min(x.def.width, 0.6) : x.def.width }));
1534
+ const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
1535
+ while (its.length && !fits(its)) {
1536
+ const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
1537
+ if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
1538
+ else its.pop();
1539
+ }
1540
+ if (!its.length) continue;
1541
+ const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
1542
+ let pos = g.start + space;
1543
+ for (const x of its) {
1544
+ const c = pos + x.width / 2;
1545
+ pos += x.width + space;
1546
+ const u = g.edge.axis === "u" ? c : g.edge.pos;
1547
+ const v = g.edge.axis === "u" ? g.edge.pos : c;
1548
+ let y0 = x.def.y0 || 1;
1549
+ let y1 = x.def.y1 || 2.15;
1550
+ if (bath) {
1551
+ y0 = Math.max(y0, 2.1);
1552
+ y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
1553
+ }
1554
+ found.push({ id: "", floor: floorIndex, u: r2(u), v: r2(v), axis: g.edge.axis, pos: g.edge.pos, at: r2(c), width: x.width, y0: r2(y0), y1: r2(y1), room: x.def.room, side: x.def.side, _order: x.order });
1555
+ }
1556
+ }
1557
+ for (const room of list) {
1558
+ if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
1559
+ let best = null;
1560
+ for (const t of stretches) {
1561
+ if (!t.sides.includes(room.id)) continue;
1562
+ const i0 = t.axis === "u" ? t.u0 : t.v0;
1563
+ const i1 = t.axis === "u" ? t.u1 : t.v1;
1564
+ const length = i1 - i0;
1565
+ const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
1566
+ if (width < 0.6 - TOL || best && best.length >= length) continue;
1567
+ const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
1568
+ const c = (i0 + i1) / 2;
1569
+ const pos = t.axis === "u" ? t.v0 : t.u0;
1570
+ const bath = room.type === "bathroom";
1571
+ best = { length, window: { id: "", floor: floorIndex, u: r2(t.axis === "u" ? c : pos), v: r2(t.axis === "u" ? pos : c), axis: t.axis, pos, at: r2(c), width: r2(width), y0: bath ? 2.1 : 1, y1: bath ? Math.min(2.5, ceiling - 0.25) : 2.15, room: room.id, side, _order: 1e6 + found.length } };
1572
+ }
1573
+ if (best) found.push(best.window);
1574
+ }
1575
+ found.sort((x, y) => x._order - y._order);
1576
+ found.forEach((j, i) => {
1577
+ delete j._order;
1578
+ j.id = "w" + floorIndex + "_" + i;
1579
+ });
1580
+ return found;
1581
+ }
1171
1582
  function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1172
1583
  const seed = op.seed == null ? 1 : op.seed;
1173
1584
  const rFacade = createRng(seed, STREAM.facade);
@@ -1227,9 +1638,11 @@ var GameFloorplan = (() => {
1227
1638
  parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
1228
1639
  addZone("garden", "garden", "garden", parts, libs.garden);
1229
1640
  }
1230
- if (backDepth > 0 && cfg.yardZones) {
1231
- for (const q of cfg.yardZones) addZone(q.id, q.type, q.label || q.id, [[-marginL, d + q.v0, w + marginR, d + Math.min(backDepth, q.v1)]], q.library);
1641
+ const yard = cfg.yardZones;
1642
+ if (backDepth > 0 && yard) {
1643
+ for (const q of yard) addZone(q.id, q.type, q.label || q.id, [[-marginL, d + q.v0, w + marginR, d + Math.min(backDepth, q.v1)]], q.library);
1232
1644
  } else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
1645
+ for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
1233
1646
  if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
1234
1647
  if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
1235
1648
  const gates = [];
@@ -1287,12 +1700,14 @@ var GameFloorplan = (() => {
1287
1700
  paths.push({ id: "cargo_path", type: "vehicle", from: g.id, to: pt.id, width: hw * 2, rects: [[g.u - hw, d, g.u + hw, d + backDepth].map(r2)], points: [[g.u, d + backDepth], [g.u, d]] });
1288
1701
  }
1289
1702
  }
1703
+ if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
1704
+ else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES2, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
1290
1705
  plan.paths = paths;
1291
1706
  lot.paths = paths;
1292
1707
  const boundary = (rule) => {
1293
1708
  const type = pickWeighted(rLot, rule.type);
1294
1709
  const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
1295
- const wallHeight = type === "railing" ? 1.2 : type === "fence" ? 2 : style.height;
1710
+ const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
1296
1711
  return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
1297
1712
  };
1298
1713
  const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
@@ -1562,7 +1977,7 @@ var GameFloorplan = (() => {
1562
1977
  }
1563
1978
  return roomCenter;
1564
1979
  };
1565
- const free = (rect) => !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1980
+ const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1566
1981
  const rule = def.facing || "center";
1567
1982
  let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
1568
1983
  if (def.anchor === "wall" || def.anchor === "window") {
@@ -1808,6 +2223,8 @@ var GameFloorplan = (() => {
1808
2223
  };
1809
2224
  if (tpl.singleFloor) result.floorNumber = op.floor || 3;
1810
2225
  result.partyWalls = tpl.partyWalls || [];
2226
+ if (plan.config) result.config = plan.config;
2227
+ runResultExtensions(plan, result, seed, op);
1811
2228
  if (zone) {
1812
2229
  const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
1813
2230
  result.zoning = report;
@@ -2077,6 +2494,214 @@ var GameFloorplan = (() => {
2077
2494
  }
2078
2495
  };
2079
2496
 
2497
+ // src/silhouettes/random.ts
2498
+ function createSilhouetteRandom(seed) {
2499
+ let state = seed;
2500
+ return () => {
2501
+ state |= 0;
2502
+ state = state + 1831565813 | 0;
2503
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
2504
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2505
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2506
+ };
2507
+ }
2508
+
2509
+ // src/silhouettes/builder.ts
2510
+ var ROOT = -1;
2511
+ var MAX_BOX_RADIUS = 0.15;
2512
+ var BOX_RADIUS_RATIO = 0.22;
2513
+ var PLAIN_BOX_RADIUS = 0.012;
2514
+ function boxRadius(width, height, depth) {
2515
+ const radius = Math.min(Math.min(width, height, depth) * BOX_RADIUS_RATIO, MAX_BOX_RADIUS);
2516
+ return radius < PLAIN_BOX_RADIUS ? 0 : radius;
2517
+ }
2518
+ function createSilhouetteBuilder(random) {
2519
+ const parts = [];
2520
+ const windows = [];
2521
+ const pick = (list) => list[Math.floor(random() * list.length)];
2522
+ const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
2523
+ const box = (x, y, z, width, height, depth, color, o = {}) => {
2524
+ parts.push({ shape: "box", parent: o.parent ?? ROOT, x, y: y + height / 2, z, width, height, depth, color, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) });
2525
+ };
2526
+ const prism = (x, y, z, width, height, depth, color, parent = ROOT) => {
2527
+ parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
2528
+ };
2529
+ const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = 14) => {
2530
+ parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
2531
+ };
2532
+ const line = (points, color, parent = ROOT) => {
2533
+ parts.push({ shape: "line", parent, points, color });
2534
+ };
2535
+ const sign = (text, x, y, z, width, height, ry, colors, parent = ROOT) => {
2536
+ parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors });
2537
+ };
2538
+ const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
2539
+ const length = Math.hypot(x1 - x0, z1 - z0);
2540
+ const count = Math.ceil(length / 3);
2541
+ for (let index = 0; index <= count; index++) {
2542
+ const t = index / count;
2543
+ cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, parent, 5);
2544
+ }
2545
+ for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
2546
+ };
2547
+ const finish = (footprint) => ({ parts, windows, footprint });
2548
+ return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
2549
+ }
2550
+
2551
+ // src/silhouettes/building.ts
2552
+ var STOREY_HEIGHT = 3;
2553
+ var SHOP_AWNING_HEIGHT = 4.5;
2554
+ var WINDOW_COLUMN_WIDTH = 2.6;
2555
+ var SHOP_SIGNS = ["FARM\xC1CIA", "MAGAZINE", "LOT\xC9RICA", "PADARIA", "\xD3TICA", "CAL\xC7ADOS", "BAR"];
2556
+ var SIGN_COLORS = ["#ffd27a", "#e8dfcf", "#c4483b"];
2557
+ var DEFAULT_LIT_CHANCE = 0.07;
2558
+ function createCityBuilding(p) {
2559
+ const random = p.random ?? createSilhouetteRandom(p.seed ?? 7);
2560
+ const b = createSilhouetteBuilder(random);
2561
+ const { x, z, width: w, depth: d, height: h } = p;
2562
+ b.box(x, 0, z, w, h, d, p.color);
2563
+ b.box(x, h, z, w + 0.3, 0.35, d + 0.3, 2763308);
2564
+ if (random() < 0.4) b.cylinder(x + (random() - 0.5) * (w - 3), h + 0.35, z + (random() - 0.5) * (d - 3), 0.8, 0.8, 1.4, 4932928);
2565
+ const hasShop = p.shopChance !== void 0 && p.shopChance > 0 && random() < p.shopChance;
2566
+ if (hasShop) {
2567
+ for (const face of [1, -1]) {
2568
+ const zFace = z + face * (d / 2);
2569
+ b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
2570
+ b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
2571
+ const text = b.pick(SHOP_SIGNS);
2572
+ b.sign(text, x, 5.3, zFace + face * 0.12, Math.min(w * 0.7, 8), 1, face > 0 ? 0 : Math.PI, { background: "#1a1a1c", foreground: b.pick(SIGN_COLORS) });
2573
+ }
2574
+ }
2575
+ addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
2576
+ return b.finish({ x, z, width: w, depth: d, height: h + 0.35 });
2577
+ }
2578
+ function addWindows(b, p, base) {
2579
+ const { x, z, width: w, depth: d, height: h } = p;
2580
+ const floors = Math.max(1, Math.floor((h - 1) / STOREY_HEIGHT));
2581
+ const faces = [[0, d / 2 + 0.03, w, 0], [0, -d / 2 - 0.03, w, Math.PI], [w / 2 + 0.03, 0, d, Math.PI / 2], [-w / 2 - 0.03, 0, d, -Math.PI / 2]];
2582
+ for (const [ox, oz, faceWidth, ry] of faces) {
2583
+ const columns = Math.max(1, Math.floor(faceWidth / WINDOW_COLUMN_WIDTH));
2584
+ for (let floor = 0; floor < floors; floor++) {
2585
+ const y = base + 1.8 + floor * STOREY_HEIGHT;
2586
+ for (let column = 0; column < columns; column++) {
2587
+ const u = (column - (columns - 1) / 2) * WINDOW_COLUMN_WIDTH;
2588
+ b.windows.push({ x: ox ? x + ox : x + u, y, z: ox ? z + u : z + oz, ry, width: 1.2, height: 1.4, isLit: b.random() < (p.litChance || DEFAULT_LIT_CHANCE) });
2589
+ }
2590
+ }
2591
+ }
2592
+ }
2593
+
2594
+ // src/silhouettes/houses.ts
2595
+ var LIT_WINDOW = 16757854;
2596
+ var DARK_WINDOW = 1053979;
2597
+ var DEFAULT_ROOF = 5913130;
2598
+ var randomOf = (o) => o.random ?? createSilhouetteRandom(o.seed ?? 7);
2599
+ var pane = (isLit) => ({ color: isLit ? LIT_WINDOW : DARK_WINDOW, emissive: isLit });
2600
+ function createTwoStoreyHouse(p) {
2601
+ const b = createSilhouetteBuilder(randomOf(p));
2602
+ const { width: w, depth: d } = p;
2603
+ const g = b.group(p.x, 0, p.z, p.ry);
2604
+ b.box(0, 0, 0, w, 6, d, p.color, { parent: g });
2605
+ b.prism(0, 6, 0, w + 0.4, 1.4, d + 0.4, p.roofColor ?? DEFAULT_ROOF, g);
2606
+ b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
2607
+ for (const floor of [0, 1]) {
2608
+ for (const side of [-1, 1]) {
2609
+ const { color, emissive } = pane(b.random() < 0.12);
2610
+ b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color, { parent: g, emissive });
2611
+ }
2612
+ }
2613
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2614
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: 7.4 });
2615
+ }
2616
+ function createSlabHouse(p) {
2617
+ const b = createSilhouetteBuilder(randomOf(p));
2618
+ const { width: w, depth: d, floors } = p;
2619
+ const g = b.group(p.x, 0, p.z, 0);
2620
+ for (let floor = 0; floor < floors; floor++) {
2621
+ const isTop = floor === floors - 1;
2622
+ b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
2623
+ const { color, emissive } = pane(b.random() < 0.1);
2624
+ b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color, { parent: g, emissive });
2625
+ }
2626
+ b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
2627
+ for (let level = 0; level < floors; level++) b.box(w / 2 + 0.4, level * 2.9, 0, 0.8, 0.18, 1.2, 8026740, { parent: g });
2628
+ if (b.random() < 0.6) for (let index = 0; index < 4; index++) b.box(-w * 0.3 + index * 0.4, floors * 2.9, 0, 0.02, 1, 0.02, 9079434, { parent: g });
2629
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: floors * 2.9 + 1 });
2630
+ }
2631
+ function createColonialHouse(p) {
2632
+ const b = createSilhouetteBuilder(randomOf(p));
2633
+ const { width: w } = p;
2634
+ const g = b.group(p.x, 0, p.z, 0);
2635
+ b.box(0, 0, 0, w, 4.2, 10, p.color, { parent: g });
2636
+ b.box(0, 4.2, 4.6, w, 0.8, 0.25, p.color, { parent: g });
2637
+ b.prism(0, 4.2, -0.4, w, 1.4, 9.6, 8014386, g);
2638
+ const count = Math.max(1, Math.floor(w / 2.2));
2639
+ for (let index = 0; index < count; index++) {
2640
+ const u = (index - (count - 1) / 2) * 2.2;
2641
+ const isDoor = index === 0 && p.hasDoor;
2642
+ const lit = isDoor ? false : b.random() < 0.15;
2643
+ const color = isDoor ? 3810326 : lit ? LIT_WINDOW : 2767434;
2644
+ b.box(u, 1, 5.02, 0.9, 2, 0.05, color, { parent: g, emissive: lit });
2645
+ }
2646
+ return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
2647
+ }
2648
+ function createChalet(p) {
2649
+ const b = createSilhouetteBuilder(randomOf(p));
2650
+ const g = b.group(p.x, 0, p.z, p.ry);
2651
+ b.box(0, 0, 0, 7, 0.8, 9, 5920336, { parent: g });
2652
+ b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
2653
+ b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
2654
+ b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
2655
+ const { color, emissive } = pane(b.random() < 0.3);
2656
+ b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color, { parent: g, emissive });
2657
+ b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2658
+ return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
2659
+ }
2660
+ function createCompanyHouse(p) {
2661
+ const b = createSilhouetteBuilder(randomOf(p));
2662
+ const g = b.group(p.x, 0, p.z, 0);
2663
+ b.box(0, 0, 0, 6, 3, 9, p.color ?? 14209216, { parent: g });
2664
+ b.prism(0, 3, 0, 6.4, 1.6, 9.4, 9062962, g);
2665
+ b.box(0, 0, 4.52, 0.9, 2.1, 0.05, 3811868, { parent: g });
2666
+ b.box(1.8, 1.1, 4.52, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2667
+ b.fence(-3.2, 6.5, 3.2, 6.5, 0.9, 14735560, g);
2668
+ return b.finish({ x: p.x, z: p.z, width: 6.4, depth: 13, height: 4.6 });
2669
+ }
2670
+ function createStiltHouse(p) {
2671
+ const b = createSilhouetteBuilder(randomOf(p));
2672
+ const g = b.group(p.x, 0, p.z, 0);
2673
+ for (const a of [-2, 2]) for (const c of [-3, 3]) b.cylinder(a, -1.5, c, 0.12, 0.12, 3.1, 3811868, g, 6);
2674
+ b.box(0, 1.5, 0, 5, 0.2, 7, 5916208, { parent: g });
2675
+ b.box(0, 1.7, 0, 4.6, 2.6, 6.4, p.color ?? 6969926, { parent: g });
2676
+ b.prism(0, 4.3, 0, 5.2, 1.1, 7, 8026744, g);
2677
+ b.box(0, 1.5, 5, 1.2, 0.15, 4, 5916208, { parent: g });
2678
+ return b.finish({ x: p.x, z: p.z, width: 5.2, depth: 9, height: 5.4 });
2679
+ }
2680
+ function createFarmhouse(p) {
2681
+ const b = createSilhouetteBuilder(randomOf(p));
2682
+ const g = b.group(p.x, 0, p.z, 0);
2683
+ b.box(0, 0, 0, 15, 3.2, 11, 14208952, { parent: g });
2684
+ b.prism(0, 3.2, 0, 17, 2.2, 13, 9062962, g);
2685
+ b.box(0, 0, 6.8, 15, 0.25, 2.6, 6969926, { parent: g });
2686
+ for (let index = 0; index < 6; index++) b.cylinder(-7 + index * 2.8, 0, 7.8, 0.1, 0.1, 3.2, 5916208, g, 6);
2687
+ b.box(-3, 1, 5.52, 1.2, 1.2, 0.04, LIT_WINDOW, { parent: g, emissive: true });
2688
+ return b.finish({ x: p.x, z: p.z, width: 17, depth: 13, height: 5.4 });
2689
+ }
2690
+ function createDetachedHouse(p) {
2691
+ const b = createSilhouetteBuilder(randomOf(p));
2692
+ const { width: w, depth: d } = p;
2693
+ const g = b.group(p.x, 0, p.z, p.ry);
2694
+ b.box(0, 0, 0, w, 3, d, p.color, { parent: g });
2695
+ b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
2696
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2697
+ for (const side of [-1, 1]) {
2698
+ const { color, emissive } = pane(b.random() < 0.15);
2699
+ b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color, { parent: g, emissive });
2700
+ if (p.isBoarded || b.random() < 0.3) for (const k of [-1, 1]) b.box(side * w * 0.28, 1.5 + k * 0.25, d / 2 + 0.08, 1.3, 0.14, 0.04, 7031338, { parent: g, rz: k * 0.15 });
2701
+ }
2702
+ return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
2703
+ }
2704
+
2080
2705
  // src/index.ts
2081
2706
  registerGroundItems();
2082
2707
  registerPermeableGround("lawn", "dirt");
@@ -2109,6 +2734,13 @@ var GameFloorplan = (() => {
2109
2734
  registerState,
2110
2735
  setStateDistribution,
2111
2736
  registerLocale,
2737
+ registerResultExtension,
2738
+ registerFindsProvider,
2739
+ findsFor,
2740
+ findsAnswer,
2741
+ registerViewerLayer,
2742
+ viewerLayers: VIEWER_LAYERS,
2743
+ blocksWindow,
2112
2744
  // zoning
2113
2745
  defineZone,
2114
2746
  getZone,
@@ -2123,6 +2755,7 @@ var GameFloorplan = (() => {
2123
2755
  catalog: CATALOG,
2124
2756
  libraries: LIBRARIES,
2125
2757
  styles: STYLES,
2758
+ paletteStyles: PALETTE_STYLES,
2126
2759
  createRng,
2127
2760
  mulberry32,
2128
2761
  hashString,
@@ -2130,7 +2763,7 @@ var GameFloorplan = (() => {
2130
2763
  windowZone,
2131
2764
  openingZones,
2132
2765
  color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
2133
- constants: { WALL_THICKNESS, DOOR_CLEARANCE }
2766
+ constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
2134
2767
  };
2135
2768
  return __toCommonJS(index_exports);
2136
2769
  })();