diff --git a/js/world/gameMode.js b/js/world/gameMode.js index bfc1049..8b69bc6 100644 --- a/js/world/gameMode.js +++ b/js/world/gameMode.js @@ -113,8 +113,38 @@ function handleInteraction(event) { case 'mapExit': { const { targetMap, targetX, targetY } = event.data; - warpToMap(targetMap, targetX, targetY); - console.log(`[gameMode] warped to ${targetMap} (${targetX}, ${targetY})`); + const p = worldState.player; + + // Save return point: where the player is NOW (one tile back from the exit) + // so when they leave the target map, they return here + worldState.returnPoints.push({ + fromMap: worldState.currentMap, + fromX: p.x, + fromY: p.y + }); + + // If exit has explicit coordinates, use them + // Otherwise, check for a stored return point for the target map + if (targetX != null && targetY != null) { + warpToMap(targetMap, targetX, targetY); + } else { + // Pop the most recent return point for this map + const retIdx = worldState.returnPoints.findLastIndex( + rp => rp.fromMap === targetMap + ); + if (retIdx >= 0) { + const ret = worldState.returnPoints[retIdx]; + worldState.returnPoints.splice(retIdx, 1); + warpToMap(ret.fromMap, ret.fromX, ret.fromY); + } else { + // Fallback: use map spawn point + const destMap = getMap(targetMap); + const sx = destMap?.spawn?.x ?? 0; + const sy = destMap?.spawn?.y ?? 0; + warpToMap(targetMap, sx, sy); + } + } + console.log(`[gameMode] warped to ${worldState.currentMap} (${worldState.player.x}, ${worldState.player.y})`); break; } diff --git a/js/world/maps.js b/js/world/maps.js index ec86041..661618b 100644 --- a/js/world/maps.js +++ b/js/world/maps.js @@ -1,60 +1,25 @@ /** - * maps.js - PNG-based world maps with wall coordinate arrays - * - * Each map has a pre-rendered PNG background image and defines: - * - walls: Set of "x,y" strings for collision (built from coordinate data) - * - npcs, interactions, exits as position-based objects - * - * Map images are drawn at 3x scale (16px native → 48px on screen) + * maps.js - PNG-based world maps (auto-generated by Level Editor) */ -// ==================== Wall builder helpers ==================== - -function buildWallSet(widthTiles, heightTiles, wallData) { +function buildWallSet(wallData) { const set = new Set(); for (const [row, cols] of Object.entries(wallData)) { - for (const col of cols) { - set.add(`${col},${row}`); - } + for (const col of cols) set.add(col + ',' + row); } return set; } -function wallRange(from, to) { - const cols = []; - for (let c = from; c <= to; c++) cols.push(c); - return cols; -} +function r(a, b) { const arr = []; for (let i = a; i <= b; i++) arr.push(i); return arr; } -// ==================== Map definitions ==================== +// ==================== Circuit Lab ==================== -/** - * LAB (10×12 tiles — lab.png 160×192) - * - * Visual layout from PNG: - * Row 0: Top wall — machinery/equipment - * Row 1: Machines on left, big computer right - * Row 2: Open area, desk/machine on far right - * Row 3-4: Table in center-left area - * Row 5: Open corridor - * Row 6-7: Two rows of bookshelves with gap in middle (col 4) - * Row 8-9: Open floor - * Row 10: Open floor near exit - * Row 11: Bottom wall, door at cols 4-5 - */ const labWalls = { - 0: wallRange(0, 9), // solid top wall - 1: [0, 1, 7, 8, 9], // machines on sides - 2: [0, 7, 8, 9], // desk/machine right - 3: [0, 2, 3, 9], // table center-left - 4: [0, 2, 3, 9], // table continues - 5: [0, 9], // open corridor - 6: [0, 1, 2, 6, 7, 8, 9], // bookshelves, gap at 3-5 - 7: [0, 1, 2, 6, 7, 8, 9], // bookshelves, gap at 3-5 - 8: [0, 9], // open - 9: [0, 9], // open - 10: [0, 9], // open - 11: [0, 1, 2, 3, 6, 7, 8, 9] // bottom wall, door at 4-5 + 0: [...r(0,9)], + 1: [0,1,2,3,6,7,8,9], + 3: [6,7,8], + 6: [0,1,2,3,6,7,8,9], + 7: [0,1,2,3,6,7,8,9], }; const labMap = { @@ -64,182 +29,142 @@ const labMap = { widthTiles: 10, heightTiles: 12, spawn: { x: 4, y: 10 }, - wallSet: buildWallSet(10, 12, labWalls), + wallSet: buildWallSet(labWalls), exits: [ - // Exit door — appear one tile BELOW the town door (not ON it, to avoid re-trigger) - { x: 4, y: 11, targetMap: 'town', targetX: 10, targetY: 8 }, - { x: 5, y: 11, targetMap: 'town', targetX: 10, targetY: 8 } + // No targetX/targetY — uses stored return point (the door the player entered from) + { x: 4, y: 11, targetMap: 'town' }, + { x: 5, y: 11, targetMap: 'town' }, ], npcs: [ - { - id: 'professor', - x: 5, y: 1, - facing: 'down', - dialog: [ - 'Welcome to the Circuit Lab!', - 'I\'m the Professor. We study logic gates here.', - 'Use the workshop tables to design circuits.', - 'Press TAB to open the Workshop anytime!' - ] - } + { id: 'professor', x: 5, y: 1, facing: 'down', dialog: ["Welcome to the Circuit Lab!","I\"m the Professor. We study logic gates here.","Use the workshop tables to design circuits.","Press TAB to open the Workshop anytime!"] }, ], interactions: [ - // Workshop tables (the table at rows 3-4) - { x: 2, y: 3, type: 'workshop', label: 'Workshop Table' }, - { x: 3, y: 3, type: 'workshop', label: 'Workshop Table' }, - { x: 2, y: 4, type: 'workshop', label: 'Workshop Table' }, - { x: 3, y: 4, type: 'workshop', label: 'Workshop Table' }, - // Bookshelves (interact from adjacent) - { x: 1, y: 6, type: 'sign', label: 'Bookshelf', - dialog: ['A collection of logic circuit manuals.'] }, - { x: 7, y: 6, type: 'sign', label: 'Bookshelf', - dialog: ['Advanced boolean algebra textbooks.'] }, - // Machines at top - { x: 8, y: 1, type: 'terminal', label: 'Terminal', - dialog: ['Circuit analysis terminal.', 'Connect components to solve puzzles.'] }, - // Puzzle door (machine at top-right) - { x: 8, y: 2, type: 'puzzle_door', puzzleId: 'lab_door_1', - requiredOutputs: [1, 0, 1, 1], label: 'Locked Door' } + { x: 7, y: 3, type: 'workshop', label: 'Workshop Table' }, + { x: 8, y: 3, type: 'workshop', label: 'Workshop Table' }, + { x: 6, y: 3, type: 'workshop', label: 'Workshop Table' }, + { x: 1, y: 7, type: 'sign', label: 'Bookshelf', dialog: ["A collection of logic circuit manuals."] }, + { x: 7, y: 7, type: 'sign', label: 'Bookshelf', dialog: ["Advanced boolean algebra textbooks."] }, + { x: 0, y: 1, type: 'terminal', label: 'Terminal', dialog: ["Circuit analysis terminal.","Connect components to solve puzzles."] }, ] }; -/** - * TOWN (20×18 tiles — pallet-town.png 320×288) - * - * Visual layout from PNG: - * Row 0: Top border — trees - * Row 1: Trees, fence - * Row 2-5: Left house (cols 1-5), Right big building (cols 12-18) - * Row 6: Open area, house doors - * Row 7-8: Sign, fences, open path area - * Row 9-10: Garden/flowers left, path center, fence right - * Row 11-13: Open area, another building right side - * Row 14-16: Water (left), trees (right), path center - * Row 17: Bottom border — trees/fence - */ -const townWalls = (() => { - const w = {}; - function add(row, cols) { - if (!w[row]) w[row] = []; - w[row].push(...cols); - } +// ==================== Neon Town ==================== - // Top border - add(0, wallRange(0, 19)); - add(1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19]); +const palletTownWalls = { + 0: [...r(0,19)], + 1: [0,1,2,3,4,5,6,7,8,9,10,11,18,19], + 2: [0,19], + 3: [0,4,5,6,7,12,13,14,15,19], + 4: [0,4,5,6,7,12,13,14,15,19], + 5: [0,3,4,5,6,7,11,12,13,14,15,19], + 6: [0,19], + 7: [0,19], + 8: [0,10,11,12,13,14,15,19], + 9: [0,4,5,6,7,10,11,12,13,14,15,19], + 10: [0,10,11,12,13,14,15,19], + 11: [0,10,11,13,14,15,19], + 12: [0,19], + 13: [0,10,11,12,13,14,15,19], + 14: [0,19], + 15: [0,19], + 16: [0,19], + 17: [0,1,8,9,10,11,12,13,14,15,16,17,18,19], +}; - // Left house (rows 2-5, cols 1-5) - for (let r = 2; r <= 4; r++) add(r, [0, 1, 2, 3, 4, 5, 19]); - add(5, [0, 1, 2, 4, 5, 19]); // door gap at col 3 - - // Right big building (rows 2-5, cols 12-18) - for (let r = 2; r <= 5; r++) add(r, [0, 12, 13, 14, 15, 16, 17, 18, 19]); - - // Open path rows with side borders - add(6, [0, 19]); - add(7, [0, 19]); - add(8, [0, 1, 18, 19]); - - // Garden/flowers left side + fence right - add(9, [0, 1, 2, 3, 4, 5, 18, 19]); - add(10, [0, 1, 2, 3, 4, 5, 18, 19]); - - // Open area - add(11, [0, 1, 18, 19]); - add(12, [0, 1, 18, 19]); - - // Building right + water left (rows 13-16) - add(13, [0, 1, 14, 15, 16, 17, 18, 19]); - add(14, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); - add(15, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); - add(16, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); - - // Bottom border - add(17, wallRange(0, 19)); - - return w; -})(); - -const townMap = { +const palletTownMap = { id: 'town', name: 'Neon Town', image: 'map:pallet-town', widthTiles: 20, heightTiles: 18, - spawn: { x: 10, y: 8 }, - wallSet: buildWallSet(20, 18, townWalls), + spawn: { x: 12, y: 12 }, + wallSet: buildWallSet(palletTownWalls), exits: [ - // Door into lab — appear one tile ABOVE the lab exit (not ON it, to avoid re-trigger) - { x: 10, y: 7, targetMap: 'lab', targetX: 4, targetY: 10 } + { x: 12, y: 11, targetMap: 'lab', targetX: 4, targetY: 10 }, ], npcs: [ - { - id: 'merchant', - x: 8, y: 10, - facing: 'right', - dialog: [ - 'Welcome to Neon Town!', - 'I trade in rare logic components.', - 'Craft some circuits in the Lab workshop!', - 'Some doors need special output patterns to open.' - ] - }, - { - id: 'guide', - x: 11, y: 12, - facing: 'down', - dialog: [ - 'The Circuit Lab is in the big building up north.', - 'Talk to the Professor — he\'ll teach you about logic gates!', - 'Press TAB anytime to open your Workshop.' - ] - } + { id: 'merchant', x: 8, y: 10, facing: 'right', dialog: ["Welcome to Neon Town!","I trade in rare logic components."] }, + { id: 'guide', x: 11, y: 12, facing: 'down', dialog: ["The Circuit Lab is in the big building up north.","Press TAB anytime to open your Workshop."] }, ], interactions: [ - // Left house door - { x: 3, y: 5, type: 'door', label: 'House', - dialog: ['The door is locked.', 'Nobody seems to be home.'] }, - // Sign in town center - { x: 10, y: 9, type: 'sign', label: 'Sign', - dialog: ['Welcome to Neon Town!', 'Circuit Lab ↑'] }, - // Right building sign - { x: 12, y: 6, type: 'sign', label: 'Sign', - dialog: ['CIRCUIT LAB', 'Open for research!'] } + { x: 3, y: 5, type: 'door', label: 'House', dialog: ["The door is locked."] }, + { x: 7, y: 9, type: 'sign', label: 'Sign', dialog: ["Welcome to Neon Town!","Circuit Lab ↑"] }, + { x: 11, y: 5, type: 'sign', label: 'Sign', dialog: ["CIRCUIT LAB","Open for research!"] }, ] }; -// ==================== Map registry ==================== +// ==================== House Interior ==================== -const maps = { - lab: labMap, - town: townMap +const houseA1fWalls = { }; -// ==================== Public API ==================== +const houseA1fMap = { + id: 'house-a-1f', + name: 'House Interior', + image: 'map:house-a-1f', + widthTiles: 8, + heightTiles: 8, + spawn: { x: 0, y: 0 }, + wallSet: buildWallSet(houseA1fWalls), -export function getMap(id) { - return maps[id] || null; -} + exits: [ + ], + + npcs: [ + ], + + interactions: [ + ] +}; + +// ==================== Route 1 ==================== + +const route1Walls = { +}; + +const route1Map = { + id: 'route-1', + name: 'Route 1', + image: 'map:route-1', + widthTiles: 20, + heightTiles: 36, + spawn: { x: 0, y: 0 }, + wallSet: buildWallSet(route1Walls), + + exits: [ + ], + + npcs: [ + ], + + interactions: [ + ] +}; + +// ==================== Registry ==================== + +const maps = { + 'lab': labMap, + 'town': palletTownMap, + 'house-a-1f': houseA1fMap, + 'route-1': route1Map, +}; + +export function getMap(id) { return maps[id] || null; } -/** - * Check if a tile position is a wall (using Set for O(1) lookup) - */ export function isWall(mapId, x, y) { const map = maps[mapId]; if (!map) return true; if (x < 0 || x >= map.widthTiles || y < 0 || y >= map.heightTiles) return true; - return map.wallSet.has(`${x},${y}`); + return map.wallSet.has(x + ',' + y); } -/** - * Check if a tile is walkable (not a wall and no NPC blocking) - */ export function isWalkable(mapId, x, y) { if (isWall(mapId, x, y)) return false; if (getNPC(mapId, x, y)) return false; @@ -264,9 +189,6 @@ export function getExit(mapId, x, y) { return map.exits.find(e => e.x === x && e.y === y) || null; } -// Backward compat -export function getTile(mapId, x, y) { - return isWall(mapId, x, y) ? 1 : 0; -} +export function getTile(mapId, x, y) { return isWall(mapId, x, y) ? 1 : 0; } export { maps }; diff --git a/js/world/worldState.js b/js/world/worldState.js index a56fca6..43551fe 100644 --- a/js/world/worldState.js +++ b/js/world/worldState.js @@ -40,6 +40,10 @@ export const worldState = { solvedPuzzles: [], // array of puzzleIds that have been solved activePuzzle: null, // { puzzleId, requiredOutputs, doorX, doorY } or null when no puzzle active + // Return points — remembers where the player entered each map from + // Stack of { fromMap, fromX, fromY } — push on enter, pop on exit + returnPoints: [], + // Game flags flags: { // Examples: