Replace pixel-art drawing with pre-rendered PNG map backgrounds and character/NPC sprite images from pokemon-js reference. Maps now use coordinate-based wall arrays instead of tile grids. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
253 lines
7.0 KiB
JavaScript
253 lines
7.0 KiB
JavaScript
/**
|
||
* maps.js - PNG-based world maps with wall coordinate arrays
|
||
*
|
||
* Each map has a pre-rendered PNG background image and defines:
|
||
* - walls: coordinate-based collision data { row: [col1, col2, ...] }
|
||
* - npcs, interactions, exits as position-based objects
|
||
*
|
||
* Map images are drawn at 3x scale (16px native → 48px on screen)
|
||
*/
|
||
|
||
// ==================== Map definitions ====================
|
||
|
||
/**
|
||
* MAP: LAB (10×12 tiles — lab.png is 160×192)
|
||
* Pokemon professor's lab interior
|
||
*/
|
||
const labMap = {
|
||
id: 'lab',
|
||
name: 'Circuit Lab',
|
||
image: 'map:lab',
|
||
widthTiles: 10,
|
||
heightTiles: 12,
|
||
spawn: { x: 4, y: 10 },
|
||
|
||
// Walls: { row: [col, col, ...] }
|
||
// Row 0-1: top shelves/machines
|
||
walls: {
|
||
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||
1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||
2: [0, 1, 2, 7, 8, 9],
|
||
3: [0, 3, 4, 5, 6, 9],
|
||
4: [0, 3, 4, 5, 6, 9],
|
||
5: [0, 3, 4, 5, 6, 9],
|
||
6: [0, 9],
|
||
7: [0, 9],
|
||
8: [0, 1, 2, 7, 8, 9],
|
||
9: [0, 1, 8, 9],
|
||
10: [0, 1, 2, 3, 6, 7, 8, 9],
|
||
11: [0, 1, 2, 3, 4, 6, 7, 8, 9]
|
||
},
|
||
|
||
exits: [
|
||
// Exit at bottom center — door to town
|
||
{ x: 4, y: 11, targetMap: 'town', targetX: 12, targetY: 10 },
|
||
{ x: 5, y: 11, targetMap: 'town', targetX: 12, targetY: 10 }
|
||
],
|
||
|
||
npcs: [
|
||
{
|
||
id: 'professor',
|
||
x: 4, y: 3,
|
||
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 big table in the middle of lab)
|
||
{ x: 3, y: 5, type: 'workshop', label: 'Workshop Table' },
|
||
{ x: 4, y: 5, type: 'workshop', label: 'Workshop Table' },
|
||
{ x: 5, y: 5, type: 'workshop', label: 'Workshop Table' },
|
||
{ x: 6, y: 5, type: 'workshop', label: 'Workshop Table' },
|
||
// Machine/bookshelf
|
||
{ x: 8, y: 2, type: 'terminal', label: 'Terminal',
|
||
dialog: ['Circuit analysis terminal.', 'Connect components to solve puzzles.'] },
|
||
// Puzzle door in top-right area
|
||
{ x: 8, y: 3, type: 'puzzle_door', puzzleId: 'lab_door_1',
|
||
requiredOutputs: [1, 0, 1, 1], label: 'Locked Door' }
|
||
]
|
||
};
|
||
|
||
/**
|
||
* MAP: TOWN (20×18 tiles — pallet-town.png is 320×288)
|
||
* Pokemon-style starting town with houses and paths
|
||
*/
|
||
const townMap = {
|
||
id: 'town',
|
||
name: 'Neon Town',
|
||
image: 'map:pallet-town',
|
||
widthTiles: 20,
|
||
heightTiles: 18,
|
||
spawn: { x: 9, y: 9 },
|
||
|
||
// Walls based on pallet-town visual layout
|
||
// Trees around border, houses, fences, water
|
||
walls: (() => {
|
||
const w = {};
|
||
// Helper to add walls
|
||
function addWall(row, cols) {
|
||
if (!w[row]) w[row] = [];
|
||
w[row].push(...cols);
|
||
}
|
||
function addRange(row, from, to) {
|
||
const cols = [];
|
||
for (let c = from; c <= to; c++) cols.push(c);
|
||
addWall(row, cols);
|
||
}
|
||
function addRect(rowStart, rowEnd, colStart, colEnd) {
|
||
for (let r = rowStart; r <= rowEnd; r++) addRange(r, colStart, colEnd);
|
||
}
|
||
|
||
// Top border (trees/fence) — rows 0-1
|
||
addRange(0, 0, 19);
|
||
addRange(1, 0, 19);
|
||
|
||
// Left border trees
|
||
for (let r = 2; r <= 17; r++) addWall(r, [0, 1]);
|
||
|
||
// Right border trees
|
||
for (let r = 2; r <= 17; r++) addWall(r, [18, 19]);
|
||
|
||
// Bottom border
|
||
addRange(17, 0, 19);
|
||
|
||
// House 1 (top-left area) — roughly rows 3-6, cols 3-7
|
||
addRect(3, 5, 3, 7);
|
||
|
||
// House 2 (top-right area) — rows 3-6, cols 12-16
|
||
addRect(3, 5, 12, 16);
|
||
|
||
// Fence segments
|
||
addRange(7, 2, 7);
|
||
addRange(7, 12, 17);
|
||
|
||
// Water/pond (bottom-left)
|
||
addRect(13, 15, 2, 5);
|
||
|
||
// Some trees/obstacles in bottom area
|
||
addWall(16, [2, 3, 4, 5, 6, 7]);
|
||
addWall(16, [12, 13, 14, 15, 16, 17]);
|
||
|
||
return w;
|
||
})(),
|
||
|
||
exits: [
|
||
// North exit — goes to lab
|
||
{ x: 12, y: 9, targetMap: 'lab', targetX: 4, targetY: 10 },
|
||
// Route 1 south (future)
|
||
// { x: 9, y: 17, targetMap: 'route1', targetX: 10, targetY: 0 }
|
||
],
|
||
|
||
npcs: [
|
||
{
|
||
id: 'merchant',
|
||
x: 9, y: 11,
|
||
facing: 'down',
|
||
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: 14, y: 9,
|
||
facing: 'left',
|
||
dialog: [
|
||
'The Circuit Lab is just up ahead.',
|
||
'Professor Oak.. I mean, the Professor can teach you about logic gates!',
|
||
'Press TAB anytime to open your Workshop.'
|
||
]
|
||
}
|
||
],
|
||
|
||
interactions: [
|
||
// House 1 door
|
||
{ x: 5, y: 6, type: 'door', label: 'House',
|
||
dialog: ['The door is locked.', 'Nobody seems to be home.'] },
|
||
// House 2 door
|
||
{ x: 14, y: 6, type: 'door', label: 'House',
|
||
dialog: ['This is the component shop.', 'Coming soon!'] },
|
||
// Sign
|
||
{ x: 10, y: 8, type: 'sign', label: 'Sign',
|
||
dialog: ['Welcome to Neon Town!', 'Circuit Lab ↑'] }
|
||
]
|
||
};
|
||
|
||
|
||
// ==================== Map registry ====================
|
||
|
||
const maps = {
|
||
lab: labMap,
|
||
town: townMap
|
||
};
|
||
|
||
// ==================== Public API ====================
|
||
|
||
export function getMap(id) {
|
||
return maps[id] || null;
|
||
}
|
||
|
||
/**
|
||
* Check if a tile position is a wall
|
||
*/
|
||
export function isWall(mapId, x, y) {
|
||
const map = maps[mapId];
|
||
if (!map) return true;
|
||
// Out of bounds = wall
|
||
if (x < 0 || x >= map.widthTiles || y < 0 || y >= map.heightTiles) return true;
|
||
const row = map.walls[y];
|
||
if (!row) return false;
|
||
return row.includes(x);
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Get interaction at position
|
||
*/
|
||
export function getInteraction(mapId, x, y) {
|
||
const map = maps[mapId];
|
||
if (!map) return null;
|
||
return map.interactions.find(i => i.x === x && i.y === y) || null;
|
||
}
|
||
|
||
/**
|
||
* Get NPC at position
|
||
*/
|
||
export function getNPC(mapId, x, y) {
|
||
const map = maps[mapId];
|
||
if (!map) return null;
|
||
return map.npcs.find(npc => npc.x === x && npc.y === y) || null;
|
||
}
|
||
|
||
/**
|
||
* Get exit at position
|
||
*/
|
||
export function getExit(mapId, x, y) {
|
||
const map = maps[mapId];
|
||
if (!map) return null;
|
||
return map.exits.find(e => e.x === x && e.y === y) || null;
|
||
}
|
||
|
||
// No longer needed but keep for compat — returns null always
|
||
export function getTile(mapId, x, y) {
|
||
return isWall(mapId, x, y) ? 1 : 0;
|
||
}
|
||
|
||
export { maps };
|