Files
logic-gates/js/world/gameMode.js
Jose Luis f740d96fc0 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>
2026-03-20 17:15:25 +01:00

176 lines
5.2 KiB
JavaScript

// gameMode.js - Central coordinator: switches between World and Workshop modes
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';
// Circuit editor stop function (to stop its render loop when switching modes)
import { stopCircuitLoop } from '../renderer.js';
// Circuit editor modules (registered from app.js to avoid circular deps)
let circuitEditorInit = null;
let circuitEditorDestroy = null;
let currentMode = 'none'; // 'world' | 'workshop'
/**
* Register the circuit editor's init/destroy functions.
* Called from app.js so we don't create circular imports.
*/
export function registerCircuitEditor(initFn, destroyFn) {
circuitEditorInit = initFn;
circuitEditorDestroy = destroyFn;
}
/**
* Boot the game — start in world mode
*/
export function startGame() {
// Set spawn
const map = getMap(worldState.currentMap);
if (map && map.spawn) {
setPlayerPosition(map.spawn.x, map.spawn.y);
}
// Wire up interaction handler
setInteractionHandler(handleInteraction);
// Enter world mode
enterWorldMode();
}
// ==================== Mode switching ====================
export function enterWorldMode() {
if (currentMode === 'world') return;
// Tear down workshop if active
if (currentMode === 'workshop') {
stopCircuitLoop();
if (circuitEditorDestroy) circuitEditorDestroy();
hideWorkshopUI();
}
currentMode = 'world';
worldState.mode = 'world';
showWorldUI();
initWorldRenderer();
initWorldInput();
startWorldLoop();
console.log('[gameMode] entered world mode');
}
export function enterWorkshopMode() {
if (currentMode === 'workshop') return;
// Tear down world
if (currentMode === 'world') {
stopWorldLoop();
destroyWorldInput();
hideWorldUI();
}
currentMode = 'workshop';
worldState.mode = 'workshop';
showWorkshopUI();
if (circuitEditorInit) circuitEditorInit();
console.log('[gameMode] entered workshop mode');
}
export function getCurrentMode() { return currentMode; }
// ==================== Interaction handler ====================
function handleInteraction(event) {
switch (event.type) {
case 'enterWorkshop':
enterWorkshopMode();
break;
case 'puzzleDoor': {
const inter = event.data;
if (isPuzzleSolved(inter.puzzleId)) {
// Already solved — could open door, show message, etc.
return;
}
// For now, show a hint dialog. Later: open puzzle UI
worldState.dialog = {
lines: [
'This door requires a logic circuit to open.',
`Required output pattern: [${inter.requiredOutputs.join(', ')}]`,
'Craft a component in your Workshop (TAB)!'
],
currentLine: 0,
speakerName: 'System'
};
worldState.mode = 'dialog';
break;
}
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;
warpToMap(targetMap, targetX, targetY);
console.log(`[gameMode] warped to ${targetMap} (${targetX}, ${targetY})`);
break;
}
case 'openInventory':
// TODO: inventory UI
console.log('[gameMode] inventory:', worldState.inventory);
break;
}
}
// ==================== UI visibility ====================
function showWorldUI() {
// Hide workshop-specific elements
const toolbar = document.getElementById('toolbar');
const wavePanel = document.getElementById('waveform-panel');
const canvas = document.getElementById('canvas');
if (toolbar) toolbar.style.display = 'none';
if (wavePanel) wavePanel.style.display = 'none';
if (canvas) {
canvas.style.top = '0';
canvas.style.cursor = 'default';
}
// Show back-to-world button (hidden since we're IN world)
const backBtn = document.getElementById('back-to-world-btn');
if (backBtn) backBtn.style.display = 'none';
}
function hideWorldUI() {
// Nothing special to hide — canvas stays
}
function showWorkshopUI() {
const toolbar = document.getElementById('toolbar');
const canvas = document.getElementById('canvas');
if (toolbar) toolbar.style.display = 'flex';
if (canvas) {
canvas.style.top = '56px';
canvas.style.cursor = 'default';
}
// Show back-to-world button
const backBtn = document.getElementById('back-to-world-btn');
if (backBtn) backBtn.style.display = 'flex';
}
function hideWorkshopUI() {
const toolbar = document.getElementById('toolbar');
if (toolbar) toolbar.style.display = 'none';
const backBtn = document.getElementById('back-to-world-btn');
if (backBtn) backBtn.style.display = 'none';
}