Files
siege-protocol/js/dev-console.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

254 lines
9.4 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.
// ════════════════════════════════════════════════════════════
// DEV CONSOLE — set DEV_MODE = false to disable entirely
// ════════════════════════════════════════════════════════════
const DEV_MODE = true;
(function() {
if (!DEV_MODE) return;
const el = () => document.getElementById('dev-console');
const inputEl = () => document.getElementById('dev-input');
const outputEl = () => document.getElementById('dev-output');
let history = [], histIdx = -1;
function devLog(msg, type = 'out') {
const d = outputEl();
const line = document.createElement('div');
line.className = 'dev-line ' + type;
line.textContent = msg;
d.appendChild(line);
d.scrollTop = d.scrollHeight;
}
function devClear() { outputEl().innerHTML = ''; }
// ── COMMANDS ────────────────────────────────────────────────
const COMMANDS = {
help: {
desc: 'List all commands. Usage: help [command]',
run(args) {
if (args.length) {
const c = COMMANDS[args[0]];
if (!c) return devLog(`Unknown command: ${args[0]}`, 'err');
devLog(`${args[0]}${c.desc}`, 'info');
} else {
devLog('Available commands:', 'info');
Object.entries(COMMANDS).forEach(([k,v]) => devLog(` ${k.padEnd(16)} ${v.desc}`, 'out'));
}
}
},
credits: {
desc: 'Set or add credits. Usage: credits <amount> | credits +500 | credits max',
run(args) {
if (!args.length) return devLog(`Current credits: ${G.credits}`, 'info');
const raw = args[0];
if (raw === 'max') { G.credits = 999999; }
else if (raw.startsWith('+')) { G.credits += parseInt(raw.slice(1)) || 0; }
else if (raw.startsWith('-')) { G.credits -= parseInt(raw.slice(1)) || 0; }
else { G.credits = parseInt(raw) || 0; }
G.credits = Math.max(0, Math.floor(G.credits));
updateHUD();
devLog(`Credits set to ${G.credits}`, 'ok');
}
},
hp: {
desc: 'Set tower HP. Usage: hp <amount> | hp max | hp full',
run(args) {
if (!args.length) return devLog(`Tower HP: ${G.tower.hp}/${G.tower.maxHp}`, 'info');
const raw = args[0];
const val = (raw === 'max' || raw === 'full') ? G.tower.maxHp : parseInt(raw);
G.tower.hp = Math.max(1, Math.min(G.tower.maxHp, val || 1));
updateHUD();
devLog(`Tower HP set to ${G.tower.hp}`, 'ok');
}
},
kill: {
desc: 'Kill all active enemies. Usage: kill | kill <enemyType>',
run(args) {
let killed = 0;
for (const e of G.enemies) {
if (!e.alive) continue;
if (args.length && e.defId !== args[0]) continue;
killEnemy(e, false);
killed++;
}
devLog(`Killed ${killed} enemies`, 'ok');
}
},
spawn: {
desc: 'Spawn enemies directly. Usage: spawn <id> [qty] (ids: grunt runner brute swarm phantom iceling sparkling venom titan wraith)',
run(args) {
if (!args.length) return devLog('Usage: spawn <id> [qty]', 'err');
const id = args[0];
const qty = parseInt(args[1]) || 1;
const def = ENEMY_DEFS.find(d => d.id === id);
if (!def) return devLog(`Unknown enemy: ${id}. Try: ${ENEMY_DEFS.map(d=>d.id).join(', ')}`, 'err');
for (let i = 0; i < qty; i++) openPortal(def, 1, 0);
devLog(`Spawned ${qty}× ${def.name}`, 'ok');
}
},
god: {
desc: 'Toggle tower invincibility.',
run() {
G._godMode = !G._godMode;
if (G._godMode) {
G._origBreachTower = window.breachTower;
window.breachTower = function(e) { killEnemy(e, true); };
devLog('God mode ON — tower cannot take damage', 'warn');
} else {
if (G._origBreachTower) window.breachTower = G._origBreachTower;
devLog('God mode OFF', 'ok');
}
}
},
speed: {
desc: 'Set game speed multiplier. Usage: speed <0.15> | speed 1 to reset',
run(args) {
if (!args.length) return devLog(`Current speed: ${G._speedMult || 1}×`, 'info');
const mult = parseFloat(args[0]);
if (isNaN(mult) || mult <= 0) return devLog('Invalid speed', 'err');
G._speedMult = mult;
devLog(`Speed set to ${mult}×`, 'ok');
}
},
perf: {
desc: 'Toggle perf overlay. Usage: perf on | perf off | perf toggle | perf',
run(args) {
const mode = (args[0] || '').toLowerCase();
if (!mode) return devLog(`Perf overlay: ${G._showPerfOverlay ? 'ON' : 'OFF'}`, 'info');
if (mode === 'on' || mode === '1' || mode === 'true') {
G._showPerfOverlay = true;
devLog('Perf overlay ON', 'ok');
return;
}
if (mode === 'off' || mode === '0' || mode === 'false') {
G._showPerfOverlay = false;
const overlay = document.getElementById('perf-overlay');
if (overlay) overlay.style.display = 'none';
devLog('Perf overlay OFF', 'ok');
return;
}
if (mode === 'toggle') {
G._showPerfOverlay = !G._showPerfOverlay;
if (!G._showPerfOverlay) {
const overlay = document.getElementById('perf-overlay');
if (overlay) overlay.style.display = 'none';
}
devLog(`Perf overlay ${G._showPerfOverlay ? 'ON' : 'OFF'}`, 'ok');
return;
}
devLog('Usage: perf on | perf off | perf toggle', 'err');
}
},
wave: {
desc: 'Spawn a full wave of mixed enemies. Usage: wave [count_each]',
run(args) {
const qty = parseInt(args[0]) || 3;
const ids = ['grunt','runner','brute','phantom'];
ids.forEach(id => {
const def = ENEMY_DEFS.find(d => d.id === id);
if (def) openPortal(def, qty, 0);
});
devLog(`Spawned wave: ${ids.join(', ')} ×${qty} each`, 'ok');
}
},
state: {
desc: 'Dump key game state. Usage: state | state enemies | state tower | state weapons',
run(args) {
const key = args[0] || 'summary';
if (key === 'enemies') return devLog(JSON.stringify(G.enemies.filter(e=>e.alive).map(e=>({id:e.defId,hp:e.hp,x:Math.round(e.x),y:Math.round(e.y)})),null,2), 'out');
if (key === 'tower') return devLog(JSON.stringify({hp:G.tower.hp,maxHp:G.tower.maxHp,shield:G.tower.shield,cannonAngle:Math.round(G.tower.cannonAngle*57)+'°'},null,2), 'out');
if (key === 'weapons') return devLog(JSON.stringify(G.weapons.filter(Boolean).map(w=>({id:w.defId,elements:w.elements,instanceId:w.instanceId})),null,2), 'out');
devLog(`frame:${G.frame} credits:${G.credits} score:${G.score} kills:${G.totalKills} enemies:${G.enemies.filter(e=>e.alive).length} projectiles:${G.projectiles.length}`, 'info');
}
},
clear: {
desc: 'Clear the console output.',
run() { devClear(); }
},
gameover: {
desc: 'Trigger game over screen immediately.',
run() { endGame(); devLog('Game over triggered', 'warn'); }
},
reset: {
desc: 'Restart the game (same as clicking Restart).',
run() { restartGame(); devLog('Game restarted', 'ok'); }
},
};
// ── INPUT HANDLER ────────────────────────────────────────────
function runCommand(raw) {
const trimmed = raw.trim();
if (!trimmed) return;
history.unshift(trimmed);
if (history.length > 50) history.pop();
histIdx = -1;
devLog('siege> ' + trimmed, 'info');
const parts = trimmed.split(/\s+/);
const cmd = COMMANDS[parts[0].toLowerCase()];
if (!cmd) {
devLog(`Unknown command: "${parts[0]}". Type "help" for list.`, 'err');
} else {
try { cmd.run(parts.slice(1)); }
catch(e) { devLog('Error: ' + e.message, 'err'); }
}
}
// ── KEYBOARD ─────────────────────────────────────────────────
document.addEventListener('keydown', function(e) {
if (!DEV_MODE) return;
// Tilde opens/closes
if (e.key === '`' || e.key === '~') {
e.preventDefault();
const con = el();
con.classList.toggle('open');
if (con.classList.contains('open')) {
inputEl().focus();
devLog('Dev console ready. Type "help" for commands.', 'info');
}
return;
}
// Only intercept further keys when console is open
if (!el().classList.contains('open')) return;
if (e.key === 'Enter') {
const inp = inputEl();
runCommand(inp.value);
inp.value = '';
e.preventDefault();
} else if (e.key === 'Escape') {
el().classList.remove('open');
e.preventDefault();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (histIdx < history.length - 1) histIdx++;
inputEl().value = history[histIdx] || '';
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (histIdx > 0) histIdx--;
else { histIdx = -1; inputEl().value = ''; return; }
inputEl().value = history[histIdx] || '';
}
});
})();