import { CFG } from './config.js'; import { clamp } from './util.js'; // Unified input. Game code never checks device type. export const Input = { moveX: 0, moveY: 0, aimX: 0, aimY: 0, // world coords give: false, take: false, // edge-triggered (consumed) giveHeld: false, takeHeld: false, grab: false, // held heat: false, light: false, // edge-triggered retry: false, perf: false, // F — dev frame-time readout usingGamepad: false, _mouse: { x: CFG.W / 2, y: CFG.H / 2 }, _keys: new Set(), _pad: null, _rumble: 0, }; const KEYMAP = { KeyW: 'up', ArrowUp: 'up', KeyS: 'down', ArrowDown: 'down', KeyA: 'left', ArrowLeft: 'left', KeyD: 'right', ArrowRight: 'right', }; let edge = { give: false, take: false, heat: false, light: false, retry: false, perf: false }; export function initInput(canvas, camera) { addEventListener('keydown', (e) => { if (e.repeat) return; Input._keys.add(e.code); if (e.code === 'KeyQ') edge.heat = true; if (e.code === 'KeyE') edge.light = true; if (e.code === 'KeyR') edge.retry = true; if (e.code === 'KeyF') edge.perf = true; if (e.code === 'Space') e.preventDefault(); }); addEventListener('keyup', (e) => Input._keys.delete(e.code)); canvas.addEventListener('mousemove', (e) => { const r = canvas.getBoundingClientRect(); Input._mouse.x = ((e.clientX - r.left) / r.width) * CFG.W; Input._mouse.y = ((e.clientY - r.top) / r.height) * CFG.H; Input.usingGamepad = false; }); canvas.addEventListener('mousedown', (e) => { e.preventDefault(); if (e.button === 0) edge.give = true; if (e.button === 2) edge.take = true; }); canvas.addEventListener('contextmenu', (e) => e.preventDefault()); addEventListener('blur', () => Input._keys.clear()); Input._camera = camera; } export function pollInput(camera) { // ---- gamepad ---- const pads = navigator.getGamepads ? navigator.getGamepads() : []; const pad = [...pads].find((p) => p && p.connected); Input._pad = pad || null; let mx = 0, my = 0; if (Input._keys.has('KeyA') || Input._keys.has('ArrowLeft')) mx -= 1; if (Input._keys.has('KeyD') || Input._keys.has('ArrowRight')) mx += 1; if (Input._keys.has('KeyW') || Input._keys.has('ArrowUp')) my -= 1; if (Input._keys.has('KeyS') || Input._keys.has('ArrowDown')) my += 1; let grab = Input._keys.has('Space'); if (pad) { const dz = (v) => (Math.abs(v) < 0.18 ? 0 : v); const lx = dz(pad.axes[0] || 0), ly = dz(pad.axes[1] || 0); if (lx || ly) { mx = lx; my = ly; Input.usingGamepad = true; } const rx = dz(pad.axes[2] || 0), ry = dz(pad.axes[3] || 0); if (rx || ry) { Input.usingGamepad = true; Input._padAim = { x: rx, y: ry }; } const btn = (i) => pad.buttons[i] && pad.buttons[i].pressed; if (btn(7)) { if (!Input.giveHeld) edge.give = true; Input.giveHeld = true; } else Input.giveHeld = false; if (btn(6)) { if (!Input.takeHeld) edge.take = true; Input.takeHeld = true; } else Input.takeHeld = false; if (btn(0)) grab = true; if (btn(4) && !Input._lb) edge.heat = true; Input._lb = btn(4); if (btn(5) && !Input._rb) edge.light = true; Input._rb = btn(5); } const m = Math.hypot(mx, my); if (m > 1) { mx /= m; my /= m; } Input.moveX = mx; Input.moveY = my; Input.grab = grab; Input.give = edge.give; Input.take = edge.take; Input.heat = edge.heat; Input.light = edge.light; Input.retry = edge.retry; Input.perf = edge.perf; edge = { give: false, take: false, heat: false, light: false, retry: false, perf: false }; // aim resolved in molly.js (needs her position for gamepad-relative aim) Input.mouseWorld = camera.screenToWorld(Input._mouse.x, Input._mouse.y); } export function setRumble(v) { Input._rumble = clamp(v, 0, 1); const pad = Input._pad; if (!pad) return; const act = pad.vibrationActuator; if (!act) return; if (v < 0.02) return; try { act.playEffect('dual-rumble', { duration: 90, startDelay: 0, weakMagnitude: clamp(v, 0, 1), strongMagnitude: clamp(v * 0.7, 0, 1), }); } catch (_) { /* not supported */ } } export function pulseRumble(v, ms = 160) { const pad = Input._pad; if (!pad || !pad.vibrationActuator) return; try { pad.vibrationActuator.playEffect('dual-rumble', { duration: ms, startDelay: 0, weakMagnitude: clamp(v, 0, 1), strongMagnitude: clamp(v, 0, 1), }); } catch (_) {} }