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>
This commit is contained in:
+195
@@ -0,0 +1,195 @@
|
||||
// ═══ 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(); },
|
||||
};
|
||||
|
||||
// 1–0 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;
|
||||
}
|
||||
Reference in New Issue
Block a user