// worldInput.js - Keyboard input for world mode import { worldState, advanceDialog, startDialog } from './worldState.js'; import { getMap, getInteraction, getNPC, getExit, isWalkable } from './maps.js'; import { toggleDebug } from './worldRenderer.js'; import { isBackpackOpen, openBackpack, handleBackpackInput, isNamingActive, handleNamingInput } from './inventory.js'; const keysDown = new Set(); let interactionHandler = null; export function setInteractionHandler(fn) { interactionHandler = fn; } export function initWorldInput() { document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); } export function destroyWorldInput() { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keyup', onKeyUp); keysDown.clear(); } // ---- Key handlers ---- function onKeyDown(e) { const key = e.key; keysDown.add(key); // Naming screen — route all input there if (isNamingActive()) { e.preventDefault(); handleNamingInput(key); return; } // Backpack open — route all input there if (isBackpackOpen()) { e.preventDefault(); handleBackpackInput(key); return; } // During dialog: advance on action keys if (worldState.dialog) { if (key === 'Enter' || key === ' ' || key === 'e' || key === 'E') { e.preventDefault(); if (!advanceDialog()) { // Dialog ended } } return; } // Debug overlay toggle (F3) if (key === 'F3') { e.preventDefault(); toggleDebug(); return; } // Backpack toggle (I) if (key === 'i' || key === 'I') { e.preventDefault(); openBackpack(null); return; } // Workshop shortcut (TAB) if (key === 'Tab') { e.preventDefault(); if (interactionHandler) interactionHandler({ type: 'enterWorkshop' }); return; } // Interaction (E / Enter / Space) if (key === 'e' || key === 'E' || key === 'Enter' || key === ' ') { e.preventDefault(); performInteraction(); return; } // Movement (handled in updateMovement via keysDown) const dir = keyToDir(key); if (dir) e.preventDefault(); } function onKeyUp(e) { keysDown.delete(e.key); } // ---- Direction mapping ---- function keyToDir(key) { if (key === 'ArrowUp' || key === 'w' || key === 'W') return 'up'; if (key === 'ArrowDown' || key === 's' || key === 'S') return 'down'; if (key === 'ArrowLeft' || key === 'a' || key === 'A') return 'left'; if (key === 'ArrowRight' || key === 'd' || key === 'D') return 'right'; return null; } /** Get the currently pressed direction (prioritizes most recent) */ function getHeldDirection() { // Check in order of specificity for (const key of keysDown) { const dir = keyToDir(key); if (dir) return dir; } return null; } // ---- Movement ---- const MOVE_DURATION = 0.15; // seconds per tile /** * Called each frame by the renderer. * Handles movement interpolation and starting new moves. */ export function updateMovement(dt) { const p = worldState.player; if (p.moving) { // Advance interpolation p._moveProgress = (p._moveProgress || 0) + dt / MOVE_DURATION; if (p._moveProgress >= 1) { // Snap to target p.x = p._targetX; p.y = p._targetY; p.px = 0; p.py = 0; p.moving = false; p._moveProgress = 0; // Check map exit checkMapExit(); // Continue moving if key held const dir = getHeldDirection(); if (dir) tryMove(dir); } else { // Interpolate p.px = (p._targetX - p._startX) * p._moveProgress; p.py = (p._targetY - p._startY) * p._moveProgress; } } else { // Not moving — check if direction key is held const dir = getHeldDirection(); if (dir) tryMove(dir); } } function tryMove(direction) { const p = worldState.player; p.direction = direction; let tx = p.x, ty = p.y; if (direction === 'up') ty--; else if (direction === 'down') ty++; else if (direction === 'left') tx--; else if (direction === 'right') tx++; if (!isWalkable(worldState.currentMap, tx, ty)) return; // Start movement p._startX = p.x; p._startY = p.y; p._targetX = tx; p._targetY = ty; p._moveProgress = 0; p.moving = true; } // ---- Interaction ---- function performInteraction() { if (worldState.player.moving) return; const p = worldState.player; let fx = p.x, fy = p.y; if (p.direction === 'up') fy--; else if (p.direction === 'down') fy++; else if (p.direction === 'left') fx--; else if (p.direction === 'right') fx++; // NPC? const npc = getNPC(worldState.currentMap, fx, fy); if (npc && npc.dialog) { startDialog(npc.dialog, npc.id); return; } // Interaction tile? const inter = getInteraction(worldState.currentMap, fx, fy); if (!inter) return; switch (inter.type) { case 'workshop': if (interactionHandler) interactionHandler({ type: 'enterWorkshop', data: inter }); break; case 'puzzle_door': if (interactionHandler) interactionHandler({ type: 'puzzleDoor', data: inter }); break; default: if (inter.dialog) startDialog(inter.dialog, ''); break; } } // ---- Map transitions ---- function checkMapExit() { const p = worldState.player; const exit = getExit(worldState.currentMap, p.x, p.y); if (exit && interactionHandler) { interactionHandler({ type: 'mapExit', data: { targetMap: exit.targetMap, targetX: exit.targetX, targetY: exit.targetY } }); } }