Files
slaguru666andClaude Opus 4.8 100e514c4d Mapwright v0.5.0 — procedural battle map generator for Foundry VTT
Buildings (modern/fantasy, multi-floor, footprint shapes), caves, outdoor
biomes, and town/village + city-block settlements. Auto-places Foundry walls,
doors, windows, lighting; furniture as locked tiles; live preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:05:25 +01:00

151 lines
7.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Furniture piece registry + room-type mapping.
*
* Each piece declares a footprint (in grid cells) and a draw() that renders the
* art to FILL a box of (wpx × hpx), centred, on a transparent background. The
* same registry feeds (a) the live preview vector, (b) the sprite PNGs used for
* locked Tile placement in the scene. System-agnostic, no external art.
*/
export const PAL = {
modern: { wood: "#9a8f80", woodLine: "#5d5448", metal: "#9aa3ad", metalLine: "#5b636c", fabric: "#7d8ba0", line: "#3a3f45", soft: "#c4ccd4", seat: "#8b95a3" },
fantasy: { wood: "#9c7b50", woodLine: "#4f3a20", metal: "#8a8d92", metalLine: "#4a4d52", fabric: "#8a6b52", line: "#3a2614", soft: "#c8b48f", seat: "#7a5532" }
};
const r2 = (n) => +n.toFixed(1);
/** Map a room label to furniture kinds (in placement priority order). */
export function piecesFor(label) {
const l = label.toLowerCase();
if (/bed|chamber/.test(l)) return ["bed", "chest"];
if (/exec|study/.test(l)) return ["desk", "shelf", "chair"];
if (/office/.test(l)) return ["desk", "chair", "shelf"];
if (/meeting|common|dining|ballroom/.test(l)) return ["bigtable"];
if (/kitchen/.test(l)) return ["counter", "table"];
if (/server/.test(l)) return ["racks", "racks"];
if (/restroom|privy|bath/.test(l)) return ["toilet", "sink"];
if (/storage|store/.test(l)) return ["crates", "shelf", "crates"];
if (/strong|vault|cellar/.test(l)) return ["chest", "chest"];
if (/reception|lobby|entry|foyer|hall/.test(l)) return ["counter"];
if (/guard|barracks/.test(l)) return ["bunk", "table"];
if (/break/.test(l)) return ["table", "counter"];
return ["table"];
}
/**
* Piece registry. footprint = [wCells, hCells] in the piece's native (un-rotated)
* orientation. `wall` true → prefers sitting against a wall. `collide` false →
* decorative, never blocks (and may overlap).
*/
export const PIECES = {
bed: { footprint: [1, 2], wall: true, draw: drawBed },
bunk: { footprint: [1, 2], wall: true, draw: drawBed },
chest: { footprint: [1, 1], wall: true, draw: drawChest },
desk: { footprint: [2, 1], wall: true, draw: drawDesk },
chair: { footprint: [1, 1], wall: false, draw: drawChair },
table: { footprint: [1, 1], wall: false, draw: drawTable },
bigtable: { footprint: [3, 2], wall: false, draw: drawBigTable },
counter: { footprint: [2, 1], wall: true, draw: drawCounter },
racks: { footprint: [1, 1], wall: true, draw: drawRacks },
toilet: { footprint: [1, 1], wall: true, draw: drawToilet },
sink: { footprint: [1, 1], wall: true, draw: drawSink },
crates: { footprint: [1, 1], wall: true, draw: drawCrates },
shelf: { footprint: [1, 1], wall: true, draw: drawShelf }
};
/** Full standalone transparent SVG for one piece — used to make sprite PNGs. */
export function pieceSpriteSVG(kind, subtype, wpx, hpx) {
const def = PIECES[kind];
const pal = PAL[subtype] ?? PAL.modern;
const inner = def ? def.draw(wpx, hpx, pal) : "";
return `<svg xmlns="http://www.w3.org/2000/svg" width="${Math.round(wpx)}" height="${Math.round(hpx)}" viewBox="0 0 ${wpx} ${hpx}">${inner}</svg>`;
}
/** Inner SVG for a piece (no <svg> wrapper) — used by the preview renderer. */
export function pieceInnerSVG(kind, subtype, wpx, hpx) {
const def = PIECES[kind];
const pal = PAL[subtype] ?? PAL.modern;
return def ? def.draw(wpx, hpx, pal) : "";
}
// --- piece art (drawn to fill a wpx×hpx box, small inset, transparent bg) -----
function box(w, h, inset) {
const m = Math.min(w, h) * inset;
return { x: r2(m), y: r2(m), w: r2(w - 2 * m), h: r2(h - 2 * m), m: r2(m) };
}
function drawBed(w, h, pal) {
const b = box(w, h, 0.08);
const pillow = r2(b.h * 0.26);
return `<g stroke-linejoin="round">` +
`<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.06)}" fill="${pal.fabric}" stroke="${pal.line}" stroke-width="${r2(w * 0.03)}"/>` +
`<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${pillow}" rx="${r2(w * 0.05)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.02)}"/></g>`;
}
function drawChest(w, h, pal) {
const b = box(w, h, 0.18);
return `<g><rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.04)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.04)}"/>` +
`<line x1="${b.x}" y1="${r2(b.y + b.h * 0.4)}" x2="${r2(b.x + b.w)}" y2="${r2(b.y + b.h * 0.4)}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.03)}"/></g>`;
}
function drawDesk(w, h, pal) {
const b = box(w, h, 0.1);
return `<g><rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(h * 0.06)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(h * 0.05)}"/>` +
`<rect x="${r2(b.x + b.w * 0.55)}" y="${r2(b.y + b.h * 0.18)}" width="${r2(b.w * 0.3)}" height="${r2(b.h * 0.5)}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(h * 0.03)}"/></g>`;
}
function drawChair(w, h, pal) {
const b = box(w, h, 0.26);
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.12)}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.04)}"/>`;
}
function drawTable(w, h, pal) {
const b = box(w, h, 0.16);
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.08)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.05)}"/>`;
}
function drawBigTable(w, h, pal) {
const b = box(w, h, 0.2);
let s = `<g>`;
// chairs around
const cr = r2(Math.min(w, h) * 0.07);
for (let i = 0; i < 3; i += 1) {
const cx = r2(b.x + b.w * (0.2 + i * 0.3));
s += `<circle cx="${cx}" cy="${r2(b.y - cr * 1.2)}" r="${cr}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.012)}"/>`;
s += `<circle cx="${cx}" cy="${r2(b.y + b.h + cr * 1.2)}" r="${cr}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.012)}"/>`;
}
s += `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(Math.min(w, h) * 0.08)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(Math.min(w, h) * 0.04)}"/>`;
return s + `</g>`;
}
function drawCounter(w, h, pal) {
const b = box(w, h, 0.12);
const top = r2(b.h * 0.5);
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${top}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(h * 0.05)}"/>`;
}
function drawRacks(w, h, pal) {
let s = `<g>`;
const b = box(w, h, 0.12);
for (let i = 0; i < 3; i += 1) {
const x = r2(b.x + (b.w / 3) * i + b.w * 0.04);
s += `<rect x="${x}" y="${b.y}" width="${r2(b.w / 3 * 0.7)}" height="${b.h}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(w * 0.025)}"/>`;
}
return s + `</g>`;
}
function drawToilet(w, h, pal) {
const cx = r2(w * 0.5);
return `<g><ellipse cx="${cx}" cy="${r2(h * 0.6)}" rx="${r2(w * 0.26)}" ry="${r2(h * 0.3)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.035)}"/>` +
`<rect x="${r2(cx - w * 0.22)}" y="${r2(h * 0.12)}" width="${r2(w * 0.44)}" height="${r2(h * 0.2)}" rx="${r2(w * 0.04)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.03)}"/></g>`;
}
function drawSink(w, h, pal) {
const b = box(w, h, 0.24);
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.1)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.035)}"/>`;
}
function drawCrates(w, h, pal) {
let s = `<g>`;
const sz = r2(Math.min(w, h) * 0.42);
for (const [ox, oy] of [[0.06, 0.1], [0.5, 0.16], [0.28, 0.52]]) {
s += `<rect x="${r2(w * ox)}" y="${r2(h * oy)}" width="${sz}" height="${sz}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.03)}"/>`;
}
return s + `</g>`;
}
function drawShelf(w, h, pal) {
const x = r2(w * 0.08), y = r2(h * 0.1), bw = r2(w * 0.84), bh = r2(h * 0.34);
return `<g><rect x="${x}" y="${y}" width="${bw}" height="${bh}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.035)}"/>` +
`<line x1="${x}" y1="${r2(y + bh / 2)}" x2="${r2(x + bw)}" y2="${r2(y + bh / 2)}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.02)}"/></g>`;
}