feat: bidirectional door system + editor bi-link tool

Replace spawn-based map transitions with explicit bidirectional door
links. Every exit now requires targetX/targetY — spawn is only used
for initial game start. Remove returnPoints stack from worldState.

Editor improvements:
- New "Bi-Link" tool creates paired exits on both maps at once
- Exit list shows target coordinates and warns if missing
- Canvas renders target info labels below exit tiles
- Properties panel handles game ID ↔ editor ID mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-20 17:15:25 +01:00
parent 1d494d8ef3
commit f740d96fc0
4 changed files with 169 additions and 52 deletions

View File

@@ -1,5 +1,5 @@
// gameMode.js - Central coordinator: switches between World and Workshop modes
import { worldState, setPlayerPosition, warpToMap, solvePuzzle, isPuzzleSolved } from './worldState.js';
import { worldState, setPlayerPosition, warpToMap, isPuzzleSolved } from './worldState.js';
import { initWorldRenderer, startWorldLoop, stopWorldLoop } from './worldRenderer.js';
import { initWorldInput, destroyWorldInput, setInteractionHandler } from './worldInput.js';
import { getMap } from './maps.js';
@@ -112,39 +112,11 @@ function handleInteraction(event) {
}
case 'mapExit': {
// Every exit MUST have targetX/targetY — bidirectional door links.
// No spawn fallback. Spawn is only for the initial game start.
const { targetMap, targetX, targetY } = event.data;
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})`);
warpToMap(targetMap, targetX, targetY);
console.log(`[gameMode] warped to ${targetMap} (${targetX}, ${targetY})`);
break;
}