Files
siege-protocol/js/dev-console.js
T
44r0n7 626879ed0c Add freshness bar, enhance overlays and renderers
- Add enemy freshness tracking (novelty bonus for repeated deploys)
- Add freshness bar to sidepanel enemy cards with penalty indicator
- Major overhaul of renderer-overlays.js (790+ lines for UI polish)
- Enhanced combat log, shop overlays, and inventory UI
- Improved weapon/upgrade display with partial ownership colors
- Added element icons and weakness/resistance indicators to cards
- Enhanced radial menu and tooltip system
- Add "stale/%" penalty text when freshness depleted
- Update play link to ffazeshift.net in index.html
2026-06-17 11:58:17 -04:00

255 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) 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] || '';
}
});
})();