/* ============================================================ * Diablo2D — input.js : unified mouse/keyboard state * Canvas-only listeners; DOM UI handles its own events. * ============================================================ */ 'use strict'; window.D2 = window.D2 || {}; (function (D2) { const state = { mx: 0, my: 0, // canvas-space cursor left: false, right: false, mid: false, leftPressed: false, // true only on frame of press rightPressed: false, wheelDelta: 0, keys: new Set(), // currently held (KeyboardEvent.code) pressedKeys: new Set(), // pressed this frame releasedKeys: new Set(), anyKeyPress: false, lastMouseWorld: null, }; let canvas = null; const _pressedQueueKeys = new Set(); const _releasedQueueKeys = new Set(); function init(targetCanvas) { canvas = targetCanvas; window.addEventListener('keydown', (e) => { if (e.repeat) return; state.keys.add(e.code); _pressedQueueKeys.add(e.code); state.anyKeyPress = true; // block browser scroll/space navigation during gameplay if (['Space', 'ArrowUp', 'ArrowDown', 'Tab'].includes(e.code)) e.preventDefault(); }, { passive: false }); window.addEventListener('keyup', (e) => { state.keys.delete(e.code); _releasedQueueKeys.add(e.code); }); window.addEventListener('blur', () => { state.keys.clear(); state.left = state.right = false; }); canvas.addEventListener('mousemove', (e) => { const r = canvas.getBoundingClientRect(); state.mx = e.clientX - r.left; state.my = e.clientY - r.top; /* authoritative resync: if the browser says buttons are up, believe it. Heals a missed mouseup (released outside the window, alt-tab, etc.) */ if (typeof e.buttons === 'number') { if (!(e.buttons & 1)) state.left = false; if (!(e.buttons & 2)) state.right = false; if (!(e.buttons & 4)) state.mid = false; } }); canvas.addEventListener('mousedown', (e) => { updateMousePos(e); if (e.button === 0) { state.left = true; state.leftPressed = true; } if (e.button === 2) { state.right = true; state.rightPressed = true; } if (e.button === 1) { state.mid = true; e.preventDefault(); } }); const releaseAll = () => { state.left = false; state.right = false; state.mid = false; }; window.addEventListener('mouseup', (e) => { if (e.button === 0) state.left = false; if (e.button === 2) state.right = false; if (e.button === 1) state.mid = false; }); /* cursor left the page / OS took the pointer / touch cancelled */ document.documentElement.addEventListener('mouseleave', releaseAll); window.addEventListener('pointercancel', releaseAll); document.addEventListener('visibilitychange', () => { if (document.hidden) releaseAll(); }); canvas.addEventListener('contextmenu', (e) => e.preventDefault()); canvas.addEventListener('wheel', (e) => { state.wheelDelta += Math.sign(e.deltaY); e.preventDefault(); }, { passive: false }); window.addEventListener('mousedown', (e) => { // audio unlock gesture D2.audio && D2.audio.unlock(); }, { once: false, capture: true }); } function updateMousePos(e) { const r = canvas.getBoundingClientRect(); state.mx = e.clientX - r.left; state.my = e.clientY - r.top; } /** call at end of each simulation frame */ function endFrame() { state.leftPressed = false; state.rightPressed = false; state.wheelDelta = 0; state.pressedKeys.clear(); state.releasedKeys.clear(); for (const k of _pressedQueueKeys) state.pressedKeys.add(k); for (const k of _releasedQueueKeys) state.releasedKeys.add(k); _pressedQueueKeys.clear(); _releasedQueueKeys.clear(); state.anyKeyPress = false; } const isDown = code => state.keys.has(code); const wasPressed = code => state.pressedKeys.has(code); const wasReleased = code => state.releasedKeys.has(code); /* virtual press used by UI hotbar buttons */ function injectPress(code) { _pressedQueueKeys.add(code); } D2.input = { state, init, endFrame, isDown, wasPressed, wasReleased, injectPress }; })(window.D2);