Files
siege-protocol/js/input.js
T
44r0n7 622a9fd170 Initial commit: Siege Protocol
Inverted tower-defense browser game — deploy enemies yourself, tower auto-kills them, pocket credits, upgrade weapons. HTML + Canvas + vanilla JS, no build step.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-16 11:36:53 -04:00

196 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ═══ input.js ═══
// ============================================================
// INPUT.JS — Keyboard hotkeys, mouse interaction
// ============================================================
const HOTKEYS = {
'Space': () => G.shopOpen ? closeShop() : openShop(),
'Escape': () => {
if (document.body.classList.contains('inventory-open')) closeWeaponPicker();
else if (G.shopOpen) closeShop();
else togglePause();
},
'KeyP': () => { if (!G.shopOpen && !document.body.classList.contains('inventory-open')) togglePause(); },
'KeyI': () => { if (!G.shopOpen) document.body.classList.contains('inventory-open') ? closeWeaponPicker() : openWeaponPicker(-1); },
'Tab': () => { if (G.shopOpen) cycleShopTab(); },
};
// 10 keys for enemy deploy
const ENEMY_HOTKEYS = ['Digit1','Digit2','Digit3','Digit4','Digit5','Digit6','Digit7','Digit8','Digit9','Digit0'];
function initInput() {
window.addEventListener('wheel', e => { if (e.ctrlKey) e.preventDefault(); }, { passive: false });
document.addEventListener('keydown', e => {
// Block all game input when dev console is open
if (DEV_MODE && document.getElementById('dev-console').classList.contains('open')) return;
// Enemy deploy hotkeys
const idx = ENEMY_HOTKEYS.indexOf(e.code);
if (idx >= 0 && idx < ENEMY_DEFS.length && !G.shopOpen && !G.paused) {
e.preventDefault();
deployEnemy(ENEMY_DEFS[idx].id, G.sendQuantity);
return;
}
const fn = HOTKEYS[e.code];
if (fn) { e.preventDefault(); fn(); }
});
initCanvasMouse();
}
// ── CANVAS MOUSE INTERACTION ──────────────────────────────────
let _hitRegions = [];
let _hoverPt = null;
let _dragWeapon = null;
let _dragSource = null;
let _suppressNextClick = false;
function clearHitRegions() { _hitRegions.length = 0; }
function addHitRegion(x, y, w, h, action) {
_hitRegions.push({ x, y, w, h, action });
}
function isHovered(x, y, w, h) {
return _hoverPt !== null &&
_hoverPt.x >= x && _hoverPt.x < x + w &&
_hoverPt.y >= y && _hoverPt.y < y + h;
}
function canvasPt(e) {
const r = canvas.getBoundingClientRect();
return {
x: (e.clientX - r.left) * (GAME_W / r.width),
y: (e.clientY - r.top) * (GAME_H / r.height),
};
}
function initCanvasMouse() {
canvas.addEventListener('mousedown', e => {
if (e.button !== 0) return;
if (DEV_MODE && document.getElementById('dev-console').classList.contains('open')) return;
const pt = canvasPt(e);
for (const r of _dragRegions) {
if (pt.x >= r.x && pt.x < r.x + r.w && pt.y >= r.y && pt.y < r.y + r.h) {
_dragWeapon = r.weapon;
_dragSource = r.source || null;
return;
}
}
});
canvas.addEventListener('mouseup', e => {
if (!_dragWeapon) return;
const pt = canvasPt(e);
let dropped = false;
if (_dragSource?.type === 'slot') {
for (const zone of _bagDropZones) {
if (pt.x >= zone.x && pt.x < zone.x + zone.w && pt.y >= zone.y && pt.y < zone.y + zone.h) {
removeWeaponFromSlot(_dragSource.slotIndex);
dropped = true;
break;
}
}
} else {
for (const zone of _bagDropZones) {
if (pt.x >= zone.x && pt.x < zone.x + zone.w && pt.y >= zone.y && pt.y < zone.y + zone.h) {
dropped = true;
break;
}
}
}
if (!dropped) {
for (const zone of _mountDropZones) {
const dx = pt.x - zone.x, dy = pt.y - zone.y;
if (dx * dx + dy * dy <= zone.r * zone.r) {
equipWeaponInstanceToSlot(zone.slotIndex, _dragWeapon.instanceId);
dropped = true;
break;
}
}
}
if (dropped) _suppressNextClick = true;
_dragWeapon = _dragSource = null;
});
canvas.addEventListener('click', e => {
if (_suppressNextClick) { _suppressNextClick = false; return; }
if (DEV_MODE && document.getElementById('dev-console').classList.contains('open')) return;
const pt = canvasPt(e);
for (const r of _hitRegions) {
if (pt.x >= r.x && pt.x < r.x + r.w && pt.y >= r.y && pt.y < r.y + r.h) {
r.action();
return;
}
}
});
canvas.addEventListener('mousemove', e => {
_hoverPt = canvasPt(e);
let pointer = false;
for (const r of _hitRegions) {
if (_hoverPt.x >= r.x && _hoverPt.x < r.x + r.w &&
_hoverPt.y >= r.y && _hoverPt.y < r.y + r.h) {
pointer = true;
break;
}
}
if (_dragWeapon) pointer = true;
canvas.style.cursor = pointer ? 'pointer' : 'default';
});
canvas.addEventListener('mouseleave', () => {
_hoverPt = null;
canvas.style.cursor = 'default';
});
canvas.addEventListener('wheel', e => {
e.preventDefault();
if (document.body.classList.contains('inventory-open')) {
_pickerScrollY = clamp(_pickerScrollY + e.deltaY * 0.5, 0, _pickerScrollMax);
} else if (G.shopOpen) {
_shopScrollY = clamp(_shopScrollY + e.deltaY * 0.5, 0, _shopScrollMax);
} else if (_hoverPt && _hoverPt.x >= 1330) {
const pt = _hoverPt;
if (pt.y >= 112 && pt.y < 722) {
// enemy cards area — scroll enemy list
_sidePanelScrollY = clamp(_sidePanelScrollY + e.deltaY * 0.5, 0, _sidePanelScrollMax);
} else if (pt.y >= 750 && pt.y < 900) {
// combat log area
_logScrollY = clamp(_logScrollY + e.deltaY * 0.5, 0, _logScrollMax);
} else if (pt.y >= 64 && pt.y < 112) {
// deploy header — cycle send quantity
const steps = [1, 5, 10, 25, 50];
const idx = steps.indexOf(G.sendQuantity);
G.sendQuantity = e.deltaY > 0
? steps[Math.min(idx + 1, steps.length - 1)]
: steps[Math.max(idx - 1, 0)];
}
}
}, { passive: false });
canvas.addEventListener('contextmenu', e => {
e.preventDefault();
if (!G.shopOpen) return;
const pt = canvasPt(e);
for (const r of _shopRightClick) {
if (pt.x >= r.x && pt.x < r.x + r.w && pt.y >= r.y && pt.y < r.y + r.h) {
r.action(); return;
}
}
});
}
function cycleShopTab() {
const weaponTabs = getEquippedWeapons().map(w => w.instanceId);
const tabs = ['tower', 'weapons', ...weaponTabs];
const idx = tabs.indexOf(G.shopTab);
G.shopTab = tabs[(Math.max(0, idx) + 1) % tabs.length];
_shopScrollY = 0;
}