Vanilla JS + Canvas 2D, zero dependencies. Gameplay: campaign waves vs 12 dino species + 3 bosses, arena mode, 7 weapons, hero talents, pets, ult/bombs, card roguelite picks. World: day/night cycle, weather fronts (rain+lightning), Blood Moon weekends, 3 act biomes (jungle -> swamp -> ashlands with lava veins). Meta: gems economy, upgrade tree, quests, bestiary, achievements, shop (weapons/heroes/skins incl. premium), revive, ad-cap system, endless scaling past wave 25, live-painted shop/bestiary icons, pro main menu, auto-aim toggle (F). Headless smoke suite included.
186 lines
6.5 KiB
JavaScript
186 lines
6.5 KiB
JavaScript
/* 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);
|
|
});
|
|
});
|
|
});
|
|
})();
|