// Расстановка мебели по правилам для каждого типа комнаты. // Работает в сетке здания: предмет занимает клетки, «спинкой» встаёт к стене, // не перекрывает двери (зона открывания) и высокими предметами не закрывает окна. import type { Building, Prop, Vec } from '../types'; import type { DoorL, FloorG, Grid, WindowL } from './interior'; import { C } from './interior'; import * as K from './catalog'; import type { Def } from './catalog'; import { Rng } from '../util/rng'; import { distToSegment } from '../util/geom'; import { CAR_COLORS } from './props'; export interface PropL { prop: Prop; pos: Vec; rot: number; room: number; } interface Ctx { g: Grid; F: FloorG; doors: DoorL[]; windows: WindowL[]; building: Building; rng: Rng; } type Dir = 'N' | 'E' | 'S' | 'W'; const ROT: Record = { N: 0, S: Math.PI, W: -Math.PI / 2, E: Math.PI / 2 }; export function furnishFloor(ctx: Ctx): PropL[] { const { g, F, rng } = ctx; const n = g.gw * g.gh; const occ = new Uint8Array(n); // 0 свободно, 1 занято, 2 держать свободным const win = new Uint8Array(n); const out: PropL[] = []; const cellsOf = new Map(); for (let i = 0; i < n; i++) { const r = F.labels[i]; if (r < 0 || !g.mask[i]) continue; if (!cellsOf.has(r)) cellsOf.set(r, []); cellsOf.get(r)!.push(i); } const cc = (i: number): Vec => [((i % g.gw) + 0.5) * C, (((i / g.gw) | 0) + 0.5) * C]; // зоны дверей и окон const around = (a: Vec, b: Vec, r: number, fn: (i: number, p: Vec) => void) => { const x0 = Math.max(0, Math.floor((Math.min(a[0], b[0]) - r) / C)), x1 = Math.min(g.gw - 1, Math.floor((Math.max(a[0], b[0]) + r) / C)); const y0 = Math.max(0, Math.floor((Math.min(a[1], b[1]) - r) / C)), y1 = Math.min(g.gh - 1, Math.floor((Math.max(a[1], b[1]) + r) / C)); for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) { const i = y * g.gw + x; if (F.labels[i] >= 0) fn(i, cc(i)); } }; for (const d of ctx.doors) { const m: Vec = [(d.a[0] + d.b[0]) / 2, (d.a[1] + d.b[1]) / 2]; const half = Math.hypot(d.b[0] - d.a[0], d.b[1] - d.a[1]) / 2; around(d.a, d.b, half + 0.7, (i, p) => { if (distToSegment(p, d.a, d.b) < 0.95 || Math.hypot(p[0] - m[0], p[1] - m[1]) < half + 0.6) occ[i] = 2; }); } for (const w of ctx.windows) around(w.a, w.b, 0.6, (i, p) => { if (distToSegment(p, w.a, w.b) < 0.6) win[i] = 1; }); // лестницы — вне мебели for (const s of F.stairs) for (let y = s.block.y0; y < s.block.y1; y++) for (let x = s.block.x0; x < s.block.x1; x++) occ[y * g.gw + x] = 1; const lab = (x: number, y: number) => (x < 0 || y < 0 || x >= g.gw || y >= g.gh ? -1 : F.labels[y * g.gw + x]); const free = (x: number, y: number, room: number, tall?: boolean) => { if (x < 0 || y < 0 || x >= g.gw || y >= g.gh) return false; const i = y * g.gw + x; return F.labels[i] === room && g.mask[i] === 1 && occ[i] === 0 && !(tall && win[i]); }; const emit = (def: Def, pos: Vec, rot: number, room: number, cells: number[], frontCells: number[], color?: string) => { for (const i of cells) occ[i] = 1; for (const i of frontCells) if (occ[i] === 0) occ[i] = 2; out.push({ pos, rot, room, prop: { id: '', kind: def.kind, pos: [0, 0], rot: 0, w: def.w, d: def.d, shapes: def.shapes, blocksMove: def.blocksMove ?? true, blocksSight: !!def.tall, tags: def.tags, color, }, }); return true; }; /** Поставить предмет спинкой к стене. */ const atWall = (room: number, def: Def, opt: { corner?: boolean; window?: boolean; tries?: number } = {}): boolean => { const cells = cellsOf.get(room); if (!cells) return false; const A = Math.max(1, Math.ceil(def.w / C - 1e-6)), D = Math.max(1, Math.ceil(def.d / C - 1e-6)); const FD = Math.ceil((def.front ?? 0) / C - 1e-6); let best: { s: number; dir: Dir; x0: number; y0: number; x1: number; y1: number } | null = null; for (const i of cells) { const cx = i % g.gw, cy = (i / g.gw) | 0; for (const dir of ['N', 'E', 'S', 'W'] as Dir[]) { let x0: number, y0: number, x1: number, y1: number; if (dir === 'N') { x0 = cx; y0 = cy; x1 = cx + A; y1 = cy + D; } else if (dir === 'S') { x0 = cx; y0 = cy - D + 1; x1 = cx + A; y1 = cy + 1; } else if (dir === 'W') { x0 = cx; y0 = cy; x1 = cx + D; y1 = cy + A; } else { x0 = cx - D + 1; y0 = cy; x1 = cx + 1; y1 = cy + A; } let ok = true; for (let y = y0; y < y1 && ok; y++) for (let x = x0; x < x1 && ok; x++) if (!free(x, y, room, def.tall)) ok = false; if (!ok) continue; // сзади должна быть стена по всей длине let wallOk = true; if (dir === 'N') for (let x = x0; x < x1; x++) wallOk &&= lab(x, y0 - 1) !== room; if (dir === 'S') for (let x = x0; x < x1; x++) wallOk &&= lab(x, y1) !== room; if (dir === 'W') for (let y = y0; y < y1; y++) wallOk &&= lab(x0 - 1, y) !== room; if (dir === 'E') for (let y = y0; y < y1; y++) wallOk &&= lab(x1, y) !== room; if (!wallOk) continue; // перед предметом — проход let frontOk = true; for (let k = 1; k <= FD && frontOk; k++) { if (dir === 'N') for (let x = x0; x < x1; x++) frontOk &&= lab(x, y1 - 1 + k) === room && occ[(y1 - 1 + k) * g.gw + x] !== 1; if (dir === 'S') for (let x = x0; x < x1; x++) frontOk &&= lab(x, y0 - k) === room && occ[(y0 - k) * g.gw + x] !== 1; if (dir === 'W') for (let y = y0; y < y1; y++) frontOk &&= lab(x1 - 1 + k, y) === room && occ[y * g.gw + x1 - 1 + k] !== 1; if (dir === 'E') for (let y = y0; y < y1; y++) frontOk &&= lab(x0 - k, y) === room && occ[y * g.gw + x0 - k] !== 1; } if (!frontOk) continue; let s = rng.next(); if (opt.corner) { const sideA = dir === 'N' || dir === 'S' ? lab(x0 - 1, dir === 'N' ? y0 : y1 - 1) !== room : lab(dir === 'W' ? x0 : x1 - 1, y0 - 1) !== room; const sideB = dir === 'N' || dir === 'S' ? lab(x1, dir === 'N' ? y0 : y1 - 1) !== room : lab(dir === 'W' ? x0 : x1 - 1, y1) !== room; s += (sideA ? 2 : 0) + (sideB ? 2 : 0); } if (opt.window) { let wn = 0; for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) wn += win[y * g.gw + x]; s += wn ? 3 : 0; } if (!best || s > best.s) best = { s, dir, x0, y0, x1, y1 }; } } if (!best) return false; const { dir, x0, y0, x1, y1 } = best; const inset = 0.07 + def.d / 2; let pos: Vec; if (dir === 'N') pos = [((x0 + x1) / 2) * C, y0 * C + inset]; else if (dir === 'S') pos = [((x0 + x1) / 2) * C, y1 * C - inset]; else if (dir === 'W') pos = [x0 * C + inset, ((y0 + y1) / 2) * C]; else pos = [x1 * C - inset, ((y0 + y1) / 2) * C]; const used: number[] = []; for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) used.push(y * g.gw + x); const front: number[] = []; for (let k = 1; k <= FD; k++) { if (dir === 'N') for (let x = x0; x < x1; x++) front.push((y1 - 1 + k) * g.gw + x); if (dir === 'S') for (let x = x0; x < x1; x++) front.push((y0 - k) * g.gw + x); if (dir === 'W') for (let y = y0; y < y1; y++) front.push(y * g.gw + x1 - 1 + k); if (dir === 'E') for (let y = y0; y < y1; y++) front.push(y * g.gw + x0 - k); } return emit(def, pos, ROT[dir], room, used, front.filter((i) => i >= 0 && i < n)); }; /** Поставить предмет отдельно (стол, кластер), с отступом margin клеток, ближе к центру комнаты. */ const inMiddle = (room: number, def: Def, margin = 1, center?: Vec, color?: string): boolean => { const cells = cellsOf.get(room); if (!cells) return false; const c = center ?? roomPole(room); let best: { s: number; x0: number; y0: number; x1: number; y1: number; rot: boolean } | null = null; for (const rotd of [false, true]) { const A = Math.max(1, Math.ceil((rotd ? def.d : def.w) / C - 1e-6)), D = Math.max(1, Math.ceil((rotd ? def.w : def.d) / C - 1e-6)); for (const i of cells) { const x0 = i % g.gw, y0 = (i / g.gw) | 0; let ok = true; for (let y = y0 - margin; y < y0 + D + margin && ok; y++) for (let x = x0 - margin; x < x0 + A + margin && ok; x++) { const inner = x >= x0 && x < x0 + A && y >= y0 && y < y0 + D; if (inner) ok = free(x, y, room); else ok = lab(x, y) === room && occ[y * g.gw + x] !== 1; } if (!ok) continue; const s = -Math.hypot((x0 + A / 2) * C - c[0], (y0 + D / 2) * C - c[1]) + rng.next() * 0.3; if (!best || s > best.s) best = { s, x0, y0, x1: x0 + A, y1: y0 + D, rot: rotd }; } } if (!best) return false; const used: number[] = []; for (let y = best.y0; y < best.y1; y++) for (let x = best.x0; x < best.x1; x++) used.push(y * g.gw + x); return emit(def, [((best.x0 + best.x1) / 2) * C, ((best.y0 + best.y1) / 2) * C], best.rot ? Math.PI / 2 : rng.chance(0.5) ? 0 : Math.PI, room, used, [], color); }; /** Сетка одинаковых предметов (столики, парты, станки). */ const lattice = (room: number, make: () => Def, gapX: number, gapY: number, margin: number, alongLong = true, max = 400, faceRot?: number): number => { const cells = cellsOf.get(room); if (!cells) return 0; const bb = roomBox(room); const longX = bb[2] - bb[0] >= bb[3] - bb[1]; const def0 = make(); const rotd = alongLong ? !longX : longX; const A = Math.ceil((rotd ? def0.d : def0.w) / C - 1e-6), D = Math.ceil((rotd ? def0.w : def0.d) / C - 1e-6); const sx = A + Math.round(gapX / C), sy = D + Math.round(gapY / C); let placed = 0; for (let y = bb[1] + margin; y + D <= bb[3] - margin && placed < max; y += sy) for (let x = bb[0] + margin; x + A <= bb[2] - margin && placed < max; x += sx) { let ok = true; for (let yy = y - 1; yy < y + D + 1 && ok; yy++) for (let xx = x - 1; xx < x + A + 1 && ok; xx++) { const inner = xx >= x && xx < x + A && yy >= y && yy < y + D; ok = inner ? free(xx, yy, room) : lab(xx, yy) === room && occ[yy * g.gw + xx] !== 1; } if (!ok) continue; const used: number[] = []; for (let yy = y; yy < y + D; yy++) for (let xx = x; xx < x + A; xx++) used.push(yy * g.gw + xx); emit(make(), [(x + A / 2) * C, (y + D / 2) * C], faceRot ?? (rotd ? Math.PI / 2 : 0), room, used, []); placed++; } return placed; }; /** Ряды стеллажей вдоль длинной стороны комнаты с проходами. */ const rows = (room: number, make: (L: number) => Def, depth: number, aisle: number, margin: number, maxLen = 12) => { const bb = roomBox(room); const longX = bb[2] - bb[0] >= bb[3] - bb[1]; const D = Math.ceil(depth / C), step = D + Math.round(aisle / C); const [a0, a1, b0, b1] = longX ? [bb[1], bb[3], bb[0], bb[2]] : [bb[0], bb[2], bb[1], bb[3]]; for (let a = a0 + margin; a + D <= a1 - margin; a += step) { let runStart = -1; const flush = (end: number) => { let s = runStart; while (end - s >= 4) { const len = Math.min(end - s, Math.round(maxLen / C)); const L = len * C - 0.1; const used: number[] = []; for (let k = s; k < s + len; k++) for (let dd = a; dd < a + D; dd++) used.push(longX ? dd * g.gw + k : k * g.gw + dd); const pos: Vec = longX ? [((s + s + len) / 2) * C, (a + D / 2) * C] : [(a + D / 2) * C, ((s + s + len) / 2) * C]; emit(make(L), pos, longX ? 0 : Math.PI / 2, room, used, []); s += len + 3; } runStart = -1; }; for (let k = b0 + margin; k <= b1 - margin; k++) { let ok = k < b1 - margin; for (let dd = a - 1; dd < a + D + 1 && ok; dd++) { const x = longX ? k : dd, y = longX ? dd : k; const inner = dd >= a && dd < a + D; ok = inner ? free(x, y, room, true) : lab(x, y) === room && occ[y * g.gw + x] !== 1; } if (ok && runStart < 0) runStart = k; if (!ok && runStart >= 0) flush(k); } } }; const roomBox = (room: number): [number, number, number, number] => { let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity; for (const i of cellsOf.get(room) ?? []) { const x = i % g.gw, y = (i / g.gw) | 0; x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x + 1); y1 = Math.max(y1, y + 1); } return [x0, y0, x1, y1]; }; const roomPole = (room: number): Vec => { const bb = roomBox(room); return [((bb[0] + bb[2]) / 2) * C, ((bb[1] + bb[3]) / 2) * C]; }; const areaOf = (room: number) => (cellsOf.get(room)?.length ?? 0) * C * C; const shortSide = (room: number) => { const bb = roomBox(room); return Math.min(bb[2] - bb[0], bb[3] - bb[1]) * C; }; const use = ctx.building.use ?? ''; for (const [room, cells] of cellsOf) { const R = F.rooms[room]; if (R.dead || !cells.length) continue; const a = areaOf(room); switch (R.kind) { case 'bedroom': { if (a >= 10 && shortSide(room) >= 2.8) { if (!atWall(room, K.bedWithNightstands())) atWall(room, K.bed(true)); } else if (!atWall(room, K.bed(a >= 9), { corner: true })) atWall(room, K.bed(false), { corner: true }); atWall(room, K.wardrobe(a > 12 ? 1.8 : 1.2), { corner: true }); if (a > 11) atWall(room, K.desk(1.2), { window: true }); if (a > 14 && rng.chance(0.5)) atWall(room, K.armchair(), { corner: true }); if (rng.chance(0.3)) atWall(room, K.plant(), { corner: true }); break; } case 'living': { const studio = a < 30 && !F.rooms.some((r) => r.unit === R.unit && r.kind === 'bedroom' && !r.dead); if (studio) { atWall(room, K.bed(true), { corner: true }); if (!F.rooms.some((r) => r.unit === R.unit && r.kind === 'kitchen' && !r.dead)) atWall(room, K.kitchenLine(1.8)); } if (!atWall(room, K.sofaSet())) atWall(room, K.sofa(1.8)); atWall(room, K.tvStand()); if (a > 16) atWall(room, K.armchair(), { corner: true }); atWall(room, K.bookshelf(rng.pick([0.8, 1.0, 1.6]))); if (a > 20 && !studio) inMiddle(room, K.diningTable(a > 26 ? 6 : 4), 1); atWall(room, K.plant(), { corner: true }); if (rng.chance(0.5)) atWall(room, K.plant(), { corner: true }); break; } case 'kitchen': { let placed = false; for (const L of [3.6, 3.0, 2.4, 1.8, 1.2]) if (atWall(room, K.kitchenLine(L))) { placed = true; break; } if (!placed) inMiddle(room, K.kitchenLine(1.2), 0); atWall(room, K.fridge(), { corner: true }); if (a >= 8) inMiddle(room, K.diningTable(4), 1); break; } case 'bath': { if (a >= 4.2 && shortSide(room) >= 1.7) { if (!atWall(room, K.bathtub(), { corner: true })) atWall(room, K.shower(), { corner: true }); } else atWall(room, K.shower(), { corner: true }); atWall(room, K.toilet()); atWall(room, K.basin()); if (a > 6) atWall(room, K.washer()); break; } case 'wc': { const n = ctx.building.kind === 'house' || a < 5 ? 1 : Math.min(4, Math.floor(a / 2.5)); for (let k = 0; k < n; k++) atWall(room, K.toilet(), { corner: k === 0 }); atWall(room, K.basin()); if (n > 1) atWall(room, K.basin()); break; } case 'hall': { atWall(room, K.coatRack()); if (a > 5) atWall(room, K.shoeCabinet()); if (a > 9) atWall(room, K.plant(), { corner: true }); if (ctx.building.kind === 'civic' && a > 20) { atWall(room, K.counter(2.4)); atWall(room, K.sofa(2.0)); } break; } case 'landing': if (F.level === 0) atWall(room, K.mailboxes()); break; case 'lobby': { atWall(room, K.mailboxes()); atWall(room, K.plant(), { corner: true }); break; } case 'corridor': { if (rng.chance(0.4)) atWall(room, K.plant(), { corner: true }); break; } case 'utility': { atWall(room, K.washer()); atWall(room, K.boiler(), { corner: true }); atWall(room, K.shelf(1.0)); break; } case 'storage': case 'backroom': { for (let k = 0; k < 8; k++) if (!atWall(room, K.shelf(rng.pick([1.0, 1.2, 1.8])))) break; for (let k = 0; k < (a > 15 ? 3 : 1); k++) inMiddle(room, K.crate(), 1); break; } case 'sales': { if (['clothes', 'shoes', 'boutique', 'fashion'].includes(use)) { lattice(room, K.clothesRack, 1.0, 1.2, 2); atWall(room, K.counter(1.6)); for (let k = 0; k < 6; k++) if (!atWall(room, K.wallShelf(2.0))) break; } else if (['bakery', 'cafe', 'butcher', 'pastry', 'deli', 'confectionery'].includes(use) || a < 25) { if (!atWall(room, K.counter(Math.min(3.0, Math.max(1.2, shortSide(room) - 1))))) inMiddle(room, K.counter(1.2), 1); for (let k = 0; k < 3; k++) if (!atWall(room, K.wallShelf(1.6))) break; } else { // супермаркет / обычный магазин: кассы у входа, стеллажи рядами, холодильники у стен const ent = ctx.doors.find((d) => d.r2 === -1 && d.r1 === room); const nCheck = a > 400 ? 4 : a > 120 ? 2 : 1; for (let k = 0; k < nCheck; k++) inMiddle(room, K.checkout(), 1, ent ? [(ent.a[0] + ent.b[0]) / 2, (ent.a[1] + ent.b[1]) / 2] : undefined); if (use === 'supermarket' || a > 150) for (let k = 0; k < 4; k++) if (!atWall(room, K.fridgeDisplay(2.4))) break; for (let k = 0; k < 10; k++) if (!atWall(room, K.wallShelf(rng.pick([1.8, 2.4])))) break; rows(room, K.gondola, 1.0, 1.6, 2, 10); } if (a > 20) atWall(room, K.plant(), { corner: true }); break; } case 'dining': { atWall(room, K.counter(Math.min(3.0, Math.max(1.4, shortSide(room) * 0.6)))); if (lattice(room, a > 30 && rng.chance(0.5) ? K.cafeTable : () => K.diningTable(4), 0.8, 0.9, 1) === 0) inMiddle(room, K.cafeTable(), 1); atWall(room, K.plant(), { corner: true }); break; } case 'prokitchen': { for (const L of [2.4, 1.8, 1.2]) if (atWall(room, K.prepTable(L))) break; atWall(room, K.proStove()); atWall(room, K.fridge(), { corner: true }); atWall(room, K.fridge(), { corner: true }); if (a > 14) inMiddle(room, K.prepTable(1.6), 1); atWall(room, K.shelf(1.2)); break; } case 'office': { atWall(room, K.desk(1.4), { window: true }); if (a > 12) atWall(room, K.desk(1.4), { window: true }); atWall(room, K.filing(), { corner: true }); atWall(room, K.bookshelf(1.0)); if (rng.chance(0.6)) atWall(room, K.plant(), { corner: true }); break; } case 'openoffice': { lattice(room, K.officeCluster, 1.4, 1.2, 2); for (let k = 0; k < 3; k++) atWall(room, K.filing(), { corner: true }); atWall(room, K.plant(), { corner: true }); atWall(room, K.plant(), { corner: true }); break; } case 'meeting': { const bb = roomBox(room); const L = Math.max(1.6, Math.min(6, (Math.max(bb[2] - bb[0], bb[3] - bb[1]) * C) - 2.2)); if (!inMiddle(room, K.meetingTable(L), 1)) inMiddle(room, K.meetingTable(1.6), 1); atWall(room, K.board(2.0)); break; } case 'staff': { inMiddle(room, K.diningTable(4), 1); atWall(room, K.kitchenLine(1.8)); atWall(room, K.sofa(1.8)); break; } case 'classroom': { atWall(room, K.board(3.0)); inMiddle(room, K.teacherDesk(), 1); lattice(room, K.studentDesk, 0.7, 0.5, 2); atWall(room, K.bookshelf(1.6)); break; } case 'ward': { for (let k = 0; k < Math.max(1, Math.min(4, Math.floor(a / 8))); k++) if (!atWall(room, K.hospitalBed())) break; atWall(room, K.nightstand()); atWall(room, K.armchair(), { corner: true }); break; } case 'guestroom': { if (!atWall(room, K.bedWithNightstands())) if (!atWall(room, K.bed(true))) atWall(room, K.bed(false)); atWall(room, K.desk(1.2), { window: true }); atWall(room, K.wardrobe(1.0), { corner: true }); atWall(room, K.armchair(), { corner: true }); break; } case 'workshop': { for (let k = 0; k < 3; k++) atWall(room, K.workbench(rng.pick([1.6, 2.0]))); for (let k = 0; k < 4; k++) atWall(room, K.shelf(1.2)); inMiddle(room, K.machine(), 1); break; } case 'hall_ind': { const kindB = ctx.building.kind; if (kindB === 'warehouse' && !['sports_hall', 'sports_centre'].includes(use)) { rows(room, K.palletRack, 1.1, 3.2, 3, 16); lattice(room, K.pallet, 2.0, 2.0, 2, true, 6); } else if (kindB === 'farm') { lattice(room, K.pallet, 2.5, 2.5, 2, true, 8); for (let k = 0; k < 4; k++) atWall(room, K.shelf(1.8)); } else if (['sports_hall', 'sports_centre'].includes(use)) { atWall(room, K.bookshelf(2.0)); } else { lattice(room, K.machine, 2.4, 2.6, 3, true, 40); for (let k = 0; k < 4; k++) atWall(room, K.workbench(2.0)); for (let k = 0; k < 6; k++) atWall(room, K.shelf(1.8)); } break; } case 'nave': { // алтарь у дальней от входа стены, скамьи рядами поперёк const ent = ctx.doors.find((d) => d.r2 === -1); const bb = roomBox(room); const longX = bb[2] - bb[0] >= bb[3] - bb[1]; const cMid: Vec = [((bb[0] + bb[2]) / 2) * C, ((bb[1] + bb[3]) / 2) * C]; let far: Vec = cMid; if (ent) { const em: Vec = [(ent.a[0] + ent.b[0]) / 2, (ent.a[1] + ent.b[1]) / 2]; far = longX ? [em[0] < cMid[0] ? (bb[2] - 3) * C : (bb[0] + 3) * C, cMid[1]] : [cMid[0], em[1] < cMid[1] ? (bb[3] - 3) * C : (bb[1] + 3) * C]; } inMiddle(room, K.altar(), 1, far); const width = (longX ? bb[3] - bb[1] : bb[2] - bb[0]) * C; const L = Math.max(1.5, Math.min(5, (width - 2.4) / 2)); lattice(room, () => K.pew(L), 1.6, 0.5, 2, false, 60, longX ? Math.PI / 2 : 0); break; } case 'garage': { if (a > 14 && shortSide(room) >= 2.6) inMiddle(room, K.carDef(), 0, undefined, rng.pick(CAR_COLORS)); atWall(room, K.workbench(1.6)); atWall(room, K.shelf(1.2)); break; } default: break; } } return out; }