// gen_maps.js: Generate 10 Tiled-format JSON maps per PRD §10. // Produces assets/maps/*.json // Tile index reference (matches tools/gen_tileset.lua order): // NOTE: Tiled uses global tile IDs where 0 = empty and localIndex N -> global N+1 (firstgid=1). // We store raw LOCAL indices in T.* and convert to global IDs (+1, 0 stays 0=empty) on write. const T = { grass: 0, path: 1, water: 2, flowersY: 3, flowersP: 4, flowersG: 5, berry: 6, tree: 7, birdTree: 8, bush: 9, wall: 10, wallTop: 11, roof: 12, roofEdge: 13, door: 14, window: 15, floor: 16, floorRug: 17, bed: 18, counter: 19, oven: 20, shelf: 21, shelfFull: 22, table: 23, chair: 24, stool: 25, well: 26, fog: 27, fogThick: 28, whiteWall: 29, whiteFloor: 30, whiteDoor: 31, whiteBed: 32, whiteMonitor: 33, whiteTable: 34, ribbon: 35, ribbon2: 36, picnic: 37, bench: 38, sign: 39, calendar: 40, paper: 41, chart: 42, fenceH: 43, fenceV: 44, lamp: 45, potPlant: 46, barrel: 47, crate: 48, grassAlt: 49, pathH: 50, pathV: 51, pathCross: 52, waterEdge: 53, grassDark: 54, pathCorner: 55, flowerMix: 56, berryEmpty: 57, fogBand: 58, whitePillar: 59, floorEdge: 60, wallTrim: 61, grassPath: 62, transparent: 63, }; // EMPTY tile (global 0): use a sentinel; we map 63(transparent local) -> 0 empty on output, others +1. const EMPTY = 63; // local index used for "transparent/empty" const COLS = 8; // tileset columns const fs = require('fs'); const path = require('path'); const outDir = 'assets/maps'; fs.mkdirSync(outDir, { recursive: true }); // Load the tileset descriptor to embed inline. const tilesetJson = JSON.parse(fs.readFileSync('assets/tilesets/tileset.json', 'utf8')); function makeMap(name, width, height, layers) { // layers: [{name, type:'tilelayer', data:number[] (len w*h)} or {name,type:'objectgroup', objects:[]}] // Find door objects and clear obstacle tiles at their positions so the player can walk through. const objLayer = layers.find(l => l.type === 'objectgroup'); const doorTiles = new Set(); if (objLayer) { for (const o of objLayer.objects) { if (o.type === 'door' || (o.name && o.name.startsWith('door'))) { const tx = Math.floor(o.x / 16), ty = Math.floor(o.y / 16); doorTiles.add(ty * width + tx); } } } const tiledLayers = layers.map(l => { if (l.type === 'tilelayer') { // convert local indices to global tile IDs: local N -> N+1, EMPTY(63) -> 0 // also clear obstacle tiles at door positions const globalData = l.data.map((v, idx) => { if (l.name === 'obstacles' && doorTiles.has(idx)) return 0; // clear wall at door return (v === EMPTY ? 0 : v + 1); }); return { name: l.name, type: 'tilelayer', x: 0, y: 0, width, height, visible: true, opacity: 1, offsetx: 0, offsety: 0, draworder: 'right-down', data: globalData, }; } else { return { name: l.name, type: 'objectgroup', x: 0, y: 0, visible: true, opacity: 1, offsetx: 0, offsety: 0, draworder: 'index', objects: l.objects, }; } }); return { compressionlevel: -1, height, width, tilewidth: 16, tileheight: 16, type: 'map', orientation: 'orthogonal', renderorder: 'right-down', tiledversion: '1.12.2', // embed tileset inline so Phaser resolves it without external file tilesets: [{ firstgid: 1, name: tilesetJson.name, columns: tilesetJson.columns, image: '../tilesets/tileset.png', imagewidth: tilesetJson.imagewidth, imageheight: tilesetJson.imageheight, margin: 0, spacing: 0, tilecount: tilesetJson.tilecount, tilewidth: 16, tileheight: 16, tiles: tilesetJson.tiles, }], layers: tiledLayers, }; } // fill helpers function fillGrid(w, h, val) { return new Array(w * h).fill(val); } function set(g, w, x, y, v) { if (x>=0&&y>=0&&x!k.startsWith('_')).map(([k,v])=>({name:k,type:'string',value:String(v)})) }; } const maps = {}; // 1. House (12x8) interior { const w=12, h=8; const ground = fillGrid(w,h,T.floor); rectFill(ground,w,h,0,0,12,1,T.wallTop); rectFill(ground,w,h,0,h-1,12,1,T.floorEdge); const obs = fillGrid(w,h,63); // transparent borderFill(obs,w,h,0,0,12,8,T.wall); // bed bottom-left rectFill(obs,w,h,1,5,3,2,T.bed); // calendar on wall (C1) - object only // door to plaza (south center) — widen the gap so the player can't get stuck set(obs,w,5,7,63); set(obs,w,6,7,63); set(ground,w,5,7,T.door); // objects const objects = [ obj('door_plaza','door',5,7,{target:'plaza', spawn:'door_house'}), obj('bed','interact',1,5,{clue:'none'}), obj('calendar','clue',2,0,{clue:'C1'}), obj('sleep_point','interact',6,5), ]; maps.house = makeMap('house', w, h, [ {name:'ground',type:'tilelayer',data:ground}, {name:'obstacles',type:'tilelayer',data:obs}, {name:'objects',type:'objectgroup',objects}, ]); } // 2. Plaza (30x20) hub { const w=30, h=20; const ground = fillGrid(w,h,T.grass); // cross paths rectFill(ground,w,h,0,9,30,2,T.path); // horizontal rectFill(ground,w,h,14,0,2,20,T.path); // vertical // flower beds rectFill(ground,w,h,3,3,4,3,T.flowersY); rectFill(ground,w,h,23,3,4,3,T.flowersP); // twin bird tree set(ground,w,15,3,T.birdTree); // signs/decor set(ground,w,2,14,T.lamp); set(ground,w,27,14,T.lamp); // ribbons area for festival (decor placed day6) const obs = fillGrid(w,h,63); // border trees for (let x=0;x