// ════════════════════════════════════════════════════════════ // 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 | 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 | 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 ', 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 [qty] (ids: grunt runner brute swarm phantom iceling sparkling venom titan wraith)', run(args) { if (!args.length) return devLog('Usage: spawn [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.1–5> | 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) return; for (let i = 0; i < qty; i++) openPortal(def, 1, 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] || ''; } }); })();