/* Neon Rampage — bootstrap & input (keyboard, mouse, touch sticks). */ (function () { 'use strict'; var US = (window.US = window.US || {}); var input = { up: false, down: false, left: false, right: false, firing: false, aimX: 640, aimY: 360, moveVec: null // touch stick vector {x,y} }; US.input = input; var canvas, touchUI = {}; var stickMove = null, stickAim = null; // active touches function toWorld(e) { var r = canvas.getBoundingClientRect(); var g = US.game.get(); if (!g) return { x: input.aimX, y: input.aimY }; var scale = Math.min(r.width / g.W, r.height / g.H); var ox = (r.width - g.W * scale) / 2; var oy = (r.height - g.H * scale) / 2; return { x: (e.clientX - r.left - ox) / scale, y: (e.clientY - r.top - oy) / scale }; } function bindKeyboard() { window.addEventListener('keydown', function (e) { switch (e.code) { case 'KeyW': case 'ArrowUp': input.up = true; break; case 'KeyS': case 'ArrowDown': input.down = true; break; case 'KeyA': case 'ArrowLeft': input.left = true; break; case 'KeyD': case 'ArrowRight': input.right = true; break; case 'Space': input.firing = true; e.preventDefault(); break; case 'KeyR': US.game.castUlt(); break; case 'KeyF': { var on2 = US.game.toggleAutoAim(); var t3 = document.getElementById('toast'); if (t3) { t3.textContent = on2 ? '🎯 Auto-aim ON' : '✋ Auto-aim OFF'; t3.classList.remove('hidden'); clearTimeout(t3._tt); t3._tt = setTimeout(function () { t3.classList.add('hidden'); }, 1400); } break; } case 'KeyQ': US.game.useBomb(); break; case 'KeyP': case 'Escape': if (!togglePause()) { // if paused via overlay button, resume if (US.game.resume()) document.getElementById('overlay-pause').classList.add('hidden'); } break; } if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(e.code) >= 0) e.preventDefault(); }); window.addEventListener('keyup', function (e) { switch (e.code) { case 'KeyW': case 'ArrowUp': input.up = false; break; case 'KeyS': case 'ArrowDown': input.down = false; break; case 'KeyA': case 'ArrowLeft': input.left = false; break; case 'KeyD': case 'ArrowRight': input.right = false; break; case 'Space': input.firing = false; break; } }); } function togglePause() { if (US.game.pause()) { document.getElementById('overlay-pause').classList.remove('hidden'); return true; } return false; } function bindMouse() { canvas.addEventListener('mousemove', function (e) { var w = toWorld(e); input.aimX = w.x; input.aimY = w.y; }); canvas.addEventListener('mousedown', function (e) { if (e.button === 0) { var w = toWorld(e); input.aimX = w.x; input.aimY = w.y; input.firing = true; } US.audio.resume(); }); window.addEventListener('mouseup', function () { input.firing = false; }); canvas.addEventListener('contextmenu', function (e) { e.preventDefault(); }); } /* ---------------- touch: twin virtual sticks ---------------- */ function makeStick(el) { var knob = el.querySelector('.stick-knob'); var base = el.getBoundingClientRect(); var id = null, vec = { x: 0, y: 0 }; function setVec(t) { var r = el.getBoundingClientRect(); var cx = r.left + r.width / 2, cy = r.top + r.height / 2; var dx = t.clientX - cx, dy = t.clientY - cy; var max = r.width / 2; var d = Math.hypot(dx, dy); if (d > max) { dx *= max / d; dy *= max / d; } vec.x = dx / max; vec.y = dy / max; knob.style.transform = 'translate(' + dx + 'px,' + dy + 'px)'; } el.addEventListener('touchstart', function (e) { e.preventDefault(); var t = e.changedTouches[0]; id = t.identifier; setVec(t); }, { passive: false }); el.addEventListener('touchmove', function (e) { e.preventDefault(); for (var i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === id) setVec(e.changedTouches[i]); } }, { passive: false }); function end(e) { for (var i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === id) { id = null; vec.x = 0; vec.y = 0; knob.style.transform = ''; } } } el.addEventListener('touchend', end); el.addEventListener('touchcancel', end); return vec; } function isTouchDevice() { return ('ontouchstart' in window) || navigator.maxTouchPoints > 0; } function bindTouch() { if (!isTouchDevice()) return; var root = document.getElementById('touch-ui'); root.classList.remove('hidden'); touchUI.fireBtn = document.getElementById('tc-fire'); touchUI.ultBtn = document.getElementById('tc-ult'); touchUI.bombBtn = document.getElementById('tc-bomb'); stickMove = makeStick(document.getElementById('stick-move')); stickAim = makeStick(document.getElementById('stick-aim')); // live vector refs — game reads these directly each frame (no polling) input.moveVec = stickMove; input.aimVec = stickAim; touchUI.fireBtn.addEventListener('touchstart', function (e) { e.preventDefault(); input.firing = true; }, { passive: false }); touchUI.fireBtn.addEventListener('touchend', function () { input.firing = false; }); touchUI.ultBtn.addEventListener('touchstart', function (e) { e.preventDefault(); US.game.castUlt(); }, { passive: false }); touchUI.bombBtn.addEventListener('touchstart', function (e) { e.preventDefault(); US.game.useBomb(); }, { passive: false }); } /* ---------------- boot ---------------- */ window.addEventListener('DOMContentLoaded', function () { canvas = document.getElementById('game'); US.i18n.setLang(US.save.get().lang); US.ui.init(); US.game.mount(canvas); bindKeyboard(); bindMouse(); bindTouch(); // default aim ahead of player input.aimX = 800; input.aimY = 360; input.moveVec = null; // replaced by live stick ref on touch devices // unlock audio on first interaction anywhere ['pointerdown', 'keydown', 'touchstart'].forEach(function (ev) { window.addEventListener(ev, function once() { US.audio.resume(); window.removeEventListener(ev, once); }); }); }); })();