fix: make spawn optional — only required for initial map
Spawn can now be deleted in the editor (click same tile with Spawn tool, use Delete tool, or press Delete key). Interior maps no longer have spawn objects. The editor shows "None" when no spawn is set, and the generated maps.js omits the spawn field for maps without one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
65
editor.html
65
editor.html
@@ -224,7 +224,7 @@ function init() {
|
||||
|
||||
// Init map data for all maps
|
||||
for (const id of Object.keys(mapConfigs)) {
|
||||
mapData[id] = { walls: new Set(), spawn: { x: 0, y: 0 }, npcs: [], exits: [], interactions: [] };
|
||||
mapData[id] = { walls: new Set(), spawn: null, npcs: [], exits: [], interactions: [] };
|
||||
}
|
||||
|
||||
// Load current game data
|
||||
@@ -283,7 +283,7 @@ function loadCurrentGameData() {
|
||||
11: [0,1,2,3,6,7,8,9]
|
||||
};
|
||||
mapData.lab.walls = wallDataToSet(labWalls);
|
||||
mapData.lab.spawn = { x: 4, y: 10 };
|
||||
mapData.lab.spawn = null; // No spawn — player enters via door from town
|
||||
mapData.lab.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!'] }
|
||||
];
|
||||
@@ -345,12 +345,14 @@ function wallDataToSet(data) {
|
||||
function switchMap(id) {
|
||||
currentMapId = id;
|
||||
selectedEntity = null;
|
||||
// Center camera on spawn
|
||||
// Center camera on spawn or map center
|
||||
const md = mapData[id];
|
||||
const cfg = mapConfigs[id];
|
||||
if (md && cfg) {
|
||||
camera.x = -(md.spawn.x * TILE_PX - canvas.width / 2 + TILE_PX / 2);
|
||||
camera.y = -(md.spawn.y * TILE_PX - canvas.height / 2 + TILE_PX / 2);
|
||||
const cx = md.spawn ? md.spawn.x : Math.floor(cfg.widthTiles / 2);
|
||||
const cy = md.spawn ? md.spawn.y : Math.floor(cfg.heightTiles / 2);
|
||||
camera.x = -(cx * TILE_PX - canvas.width / 2 + TILE_PX / 2);
|
||||
camera.y = -(cy * TILE_PX - canvas.height / 2 + TILE_PX / 2);
|
||||
}
|
||||
updateEntityList();
|
||||
updateProps();
|
||||
@@ -419,14 +421,17 @@ function render() {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Spawn
|
||||
const sp = md.spawn;
|
||||
ctx.fillStyle = 'rgba(0, 229, 153, 0.4)';
|
||||
ctx.fillRect(sp.x * TILE_PX, sp.y * TILE_PX, TILE_PX, TILE_PX);
|
||||
ctx.strokeStyle = '#00e599';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(sp.x * TILE_PX, sp.y * TILE_PX, TILE_PX, TILE_PX);
|
||||
drawLabel(ctx, '🏠', sp.x, sp.y);
|
||||
// Spawn (optional — only needed on the starting map)
|
||||
if (md.spawn) {
|
||||
const sp = md.spawn;
|
||||
const spSel = selectedEntity?.type === 'spawn';
|
||||
ctx.fillStyle = 'rgba(0, 229, 153, 0.4)';
|
||||
ctx.fillRect(sp.x * TILE_PX, sp.y * TILE_PX, TILE_PX, TILE_PX);
|
||||
ctx.strokeStyle = spSel ? '#fff' : '#00e599';
|
||||
ctx.lineWidth = spSel ? 2.5 : 2;
|
||||
ctx.strokeRect(sp.x * TILE_PX, sp.y * TILE_PX, TILE_PX, TILE_PX);
|
||||
drawLabel(ctx, '🏠', sp.x, sp.y);
|
||||
}
|
||||
|
||||
// Exits
|
||||
md.exits.forEach((e, i) => {
|
||||
@@ -548,8 +553,15 @@ function onMouseDown(e) {
|
||||
render();
|
||||
break;
|
||||
case 'spawn':
|
||||
md.spawn = { x: tile.x, y: tile.y };
|
||||
updateEntityList(); render();
|
||||
// Toggle: if spawn already at this tile, remove it; otherwise place/move
|
||||
if (md.spawn && md.spawn.x === tile.x && md.spawn.y === tile.y) {
|
||||
md.spawn = null;
|
||||
selectedEntity = null;
|
||||
} else {
|
||||
md.spawn = { x: tile.x, y: tile.y };
|
||||
selectedEntity = { type: 'spawn', index: 0 };
|
||||
}
|
||||
updateEntityList(); updateProps(); render();
|
||||
break;
|
||||
case 'npc':
|
||||
md.npcs.push({ id: `npc_${Date.now()}`, x: tile.x, y: tile.y, facing: 'down', dialog: ['Hello!'] });
|
||||
@@ -597,7 +609,7 @@ function onMouseMove(e) {
|
||||
const key = `${tile.x},${tile.y}`;
|
||||
let info = '';
|
||||
if (md.walls.has(key)) info += 'Wall ';
|
||||
if (md.spawn.x === tile.x && md.spawn.y === tile.y) info += 'Spawn ';
|
||||
if (md.spawn && md.spawn.x === tile.x && md.spawn.y === tile.y) info += 'Spawn ';
|
||||
md.npcs.forEach(n => { if (n.x === tile.x && n.y === tile.y) info += `NPC:${n.id} `; });
|
||||
md.exits.forEach(e => { if (e.x === tile.x && e.y === tile.y) info += `Exit→${e.targetMap}(${e.targetX ?? '?'},${e.targetY ?? '?'}) `; });
|
||||
md.interactions.forEach(i => { if (i.x === tile.x && i.y === tile.y) info += `${i.type}:${i.label} `; });
|
||||
@@ -655,7 +667,8 @@ function onKeyDown(e) {
|
||||
e.preventDefault();
|
||||
const md = getData();
|
||||
const { type, index } = selectedEntity;
|
||||
if (type === 'npc') md.npcs.splice(index, 1);
|
||||
if (type === 'spawn') md.spawn = null;
|
||||
else if (type === 'npc') md.npcs.splice(index, 1);
|
||||
else if (type === 'exit') md.exits.splice(index, 1);
|
||||
else if (type === 'interaction') md.interactions.splice(index, 1);
|
||||
selectedEntity = null;
|
||||
@@ -731,13 +744,15 @@ function selectEntityAt(tx, ty) {
|
||||
idx = md.interactions.findIndex(i => i.x === tx && i.y === ty);
|
||||
if (idx >= 0) { selectedEntity = { type: 'interaction', index: idx }; updateEntityList(); updateProps(); return; }
|
||||
// Spawn?
|
||||
if (md.spawn.x === tx && md.spawn.y === ty) { selectedEntity = { type: 'spawn', index: 0 }; updateEntityList(); updateProps(); return; }
|
||||
if (md.spawn && md.spawn.x === tx && md.spawn.y === ty) { selectedEntity = { type: 'spawn', index: 0 }; updateEntityList(); updateProps(); return; }
|
||||
selectedEntity = null;
|
||||
updateEntityList(); updateProps();
|
||||
}
|
||||
|
||||
function deleteEntityAt(tx, ty) {
|
||||
const md = getData();
|
||||
// Spawn
|
||||
if (md.spawn && md.spawn.x === tx && md.spawn.y === ty) { md.spawn = null; selectedEntity = null; updateEntityList(); updateProps(); return; }
|
||||
let idx = md.npcs.findIndex(n => n.x === tx && n.y === ty);
|
||||
if (idx >= 0) { md.npcs.splice(idx, 1); selectedEntity = null; updateEntityList(); updateProps(); return; }
|
||||
idx = md.exits.findIndex(e => e.x === tx && e.y === ty);
|
||||
@@ -751,7 +766,9 @@ function deleteEntityAt(tx, ty) {
|
||||
function updateEntityList() {
|
||||
const md = getData();
|
||||
|
||||
document.getElementById('spawn-list').innerHTML = makeEntityItem('spawn', 0, '🏠', `Spawn`, md.spawn.x, md.spawn.y);
|
||||
document.getElementById('spawn-list').innerHTML = md.spawn
|
||||
? makeEntityItem('spawn', 0, '🏠', `Spawn`, md.spawn.x, md.spawn.y)
|
||||
: '<div style="padding:4px 16px;color:var(--text2);font-size:11px;">None (use 🏠 tool to place)</div>';
|
||||
|
||||
document.getElementById('npc-list').innerHTML = md.npcs.map((n, i) =>
|
||||
makeEntityItem('npc', i, '👤', n.id, n.x, n.y, '#cc55ff')
|
||||
@@ -799,7 +816,7 @@ function getSelectedEntityData() {
|
||||
if (!selectedEntity) return null;
|
||||
const md = getData();
|
||||
switch (selectedEntity.type) {
|
||||
case 'spawn': return md.spawn;
|
||||
case 'spawn': return md.spawn || null;
|
||||
case 'npc': return md.npcs[selectedEntity.index];
|
||||
case 'exit': return md.exits[selectedEntity.index];
|
||||
case 'interaction': return md.interactions[selectedEntity.index];
|
||||
@@ -954,7 +971,9 @@ function generateMapsJS() {
|
||||
out += ` image: '${imageKey}',\n`;
|
||||
out += ` widthTiles: ${cfg.widthTiles},\n`;
|
||||
out += ` heightTiles: ${cfg.heightTiles},\n`;
|
||||
out += ` spawn: { x: ${md.spawn.x}, y: ${md.spawn.y} },\n`;
|
||||
if (md.spawn) {
|
||||
out += ` spawn: { x: ${md.spawn.x}, y: ${md.spawn.y} },\n`;
|
||||
}
|
||||
out += ` wallSet: buildWallSet(${varName(mapId)}Walls),\n\n`;
|
||||
|
||||
// Exits
|
||||
@@ -1154,10 +1173,12 @@ function parseAndLoadMapsJS(src) {
|
||||
mapData[mapId].walls = wallDataToSet(wallData);
|
||||
}
|
||||
|
||||
// Parse spawn
|
||||
// Parse spawn (optional — only present on initial map)
|
||||
const spawnMatch = src.match(new RegExp(`${vName}Map[\\s\\S]*?spawn:\\s*\\{\\s*x:\\s*(\\d+)\\s*,\\s*y:\\s*(\\d+)`));
|
||||
if (spawnMatch) {
|
||||
mapData[mapId].spawn = { x: parseInt(spawnMatch[1]), y: parseInt(spawnMatch[2]) };
|
||||
} else {
|
||||
mapData[mapId].spawn = null;
|
||||
}
|
||||
|
||||
// Parse NPCs - find the npcs array
|
||||
|
||||
@@ -28,7 +28,7 @@ const labMap = {
|
||||
image: 'map:lab',
|
||||
widthTiles: 10,
|
||||
heightTiles: 12,
|
||||
spawn: { x: 4, y: 10 },
|
||||
// No spawn — player enters via door from town
|
||||
wallSet: buildWallSet(labWalls),
|
||||
|
||||
exits: [
|
||||
@@ -110,7 +110,6 @@ const houseA1fMap = {
|
||||
image: 'map:house-a-1f',
|
||||
widthTiles: 8,
|
||||
heightTiles: 8,
|
||||
spawn: { x: 0, y: 0 },
|
||||
wallSet: buildWallSet(houseA1fWalls),
|
||||
|
||||
exits: [
|
||||
@@ -134,7 +133,6 @@ const route1Map = {
|
||||
image: 'map:route-1',
|
||||
widthTiles: 20,
|
||||
heightTiles: 36,
|
||||
spawn: { x: 0, y: 0 },
|
||||
wallSet: buildWallSet(route1Walls),
|
||||
|
||||
exits: [
|
||||
|
||||
Reference in New Issue
Block a user