Files
deepgrain-foundry/src/sim/devconsole.js
T

115 lines
7.5 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 commands are pure simulation operations; the browser owns the panel and confirmation.
let devWorld = false, devLog = [], devFree = false, devSpeed = 1, devPaused = false, devSteps = 0;
function resetDevConsole() {
devWorld = false; devLog = []; devFree = false; devSpeed = 1; devPaused = false; devSteps = 0;
}
function enableDevConsole() { devWorld = true; }
const DEV_HELP = [
'help — list commands',
'money <n> | money set <n> — add or set money; negative amounts allowed',
'debt <n> | debt clear — set or clear claim debt',
'give <material> [n] — add grains or items (default 1000); display or internal name, unique prefix',
'bit [tier] — list or set drill tiers (numbered from 0)',
'bag [tier] — list or set bag tiers (numbered from 0)',
'unlock — learn all materials and unlock progress rewards',
'tp [x y] — teleport in grains; no coordinates uses the cursor',
'free on|off — build without spending money or materials',
'speed <x> — speed multiplier (0.25, 0.5, 1, 2, 4; clamped to 0.1–8)',
'pause — toggle dev pause',
'step [n] — queue 1–10000 ticks while dev paused (default 1)',
'weather clear|rain — stop current rain or start a one-minute shower',
];
// Exact internal names win when a display name names a different material.
const DEV_MATERIALS = { EMPTY, GRASS, DIRT, LDIRT, SAND, GRAVEL, CLAY, STONE, GRANITE, BASALT, WOOD, LEAVES, COALV, COAL, CUV, CUORE, FEV, FEORE, ASH, SLAG, BRICK, GLASS, COPPER, IRON, STEEL, WATER, MAGMA, MCU, MFE, MST, MGL, STEAM, SMOKE, FIRE, BELTL, BELTR, SHAFT, GATE, HULL, SPRING, RSTONE, RGRAN, RBAS, LCLAY, LWOOD, RBRICK, CULLET, SCRCU, SCRFE, SCRST, BARCU, BARFE, BARST, ACORN, CHARCOAL, SNOW, ICE, SNV, SNORE, TIN, MSN, SCRSN, BARSN, ZNV, ZNORE, ZINC, MZN, SCRZN, BARZN, AGV, AGORE, SILVER, MAG, SCRAG, BARAG, AUV, AUORE, GOLD, MAU, SCRAU, BARAU, BRONZE, MBZ, SCRBZ, BARBZ, BRASS, MBS, SCRBS, BARBS, DOCKW, MSLAG, PIPE, BARGL, CUTST, BARSB, GRINDER, SAWB, PLANK, PRESSB, GREENB, BARBR, RACKB };
function devMaterial(name) {
name = name.toLowerCase();
const ids = Object.entries(DEV_MATERIALS);
const internal = ids.find(([key]) => key.toLowerCase() === name);
if (internal) return internal[1];
const exact = ids.filter(([, id]) => MD[id].name.toLowerCase() === name);
const matches = [...new Set((exact.length ? exact : ids.filter(([key, id]) => key.toLowerCase().startsWith(name) || MD[id].name.toLowerCase().startsWith(name))).map(([, id]) => id))];
return matches.length === 1 ? matches[0] : matches.length ? `Ambiguous material: ${matches.map(id => MD[id].name + ' (' + ids.find(([, m]) => m === id)[0] + ')').join(', ')}.` : 'Unknown material. Use a display name or internal name, such as copper ore or CUORE.';
}
function devCommand(text, cursor) {
if (typeof text !== 'string' || !text.trim()) return ['Type help to list commands.'];
text = text.trim();
const [cmd, ...args] = text.toLowerCase().split(/\s+/);
const usage = () => ['Usage: ' + DEV_HELP.find(s => s.startsWith(cmd + ' '))];
const number = s => s !== undefined && Number.isFinite(Number(s));
if (cmd === 'help' && !args.length) return DEV_HELP.slice();
if (!DEV_HELP.some(s => s.startsWith(cmd + ' '))) return ['Unknown command. Type help to list commands.'];
if (!devWorld) return ['Open the dev console and accept the world warning first.'];
let result;
switch (cmd) {
case 'money': {
const set = args[0] === 'set', value = Number(args[set ? 1 : 0]);
if (args.length !== (set ? 2 : 1) || !number(args[set ? 1 : 0]) || !number(String(set ? value : money + value))) return usage();
money = set ? value : money + value; result = 'Money: ' + fmtMoney(money); break;
}
case 'debt':
if (args.length !== 1 || (args[0] !== 'clear' && (!number(args[0]) || Number(args[0]) < 0))) return usage();
debt = args[0] === 'clear' ? 0 : Number(args[0]);
result = debt ? 'Claim debt: ' + fmtMoney(debt) : 'Claim paid off.'; break;
case 'give': {
if (!args.length) return usage();
let n = 1000;
if (number(args[args.length - 1])) n = Number(args.pop());
if (!args.length || !Number.isInteger(n) || n <= 0) return usage();
const m = devMaterial(args.join(' '));
if (typeof m === 'string') return [m];
if (m === EMPTY || PH[m] === PH_GAS || (PH[m] === PH_SOLID && !YLD[m])) return [`${MD[m].name} cannot go in the bag.`];
if (n > 2147483647 - bag[m]) return ['Too many: a material count cannot exceed 2147483647.'];
const into = PH[m] === PH_SOLID && YLD[m] ? YLD[m] : m;
bagAdd(m, n, true); result = `Added ${n} ${MD[into].name}${into !== m ? ` (as ${MD[m].name.toLowerCase()} breaks up)` : ''}.`; break;
}
case 'bit': case 'bag': {
const tiers = cmd === 'bit' ? BIT_TIERS : TANK_TIERS;
if (!args.length) return tiers.map((t, i) => `${i}: ${t.name}${t.cap ? ' (' + t.cap + ' grains)' : ''}`);
if (args.length !== 1 || !number(args[0]) || !Number.isInteger(Number(args[0]))) return usage();
const tier = Math.max(0, Math.min(tiers.length - 1, Number(args[0])));
player[cmd === 'bit' ? 'bitTier' : 'tankTier'] = tier;
result = `${cmd}: ${tier} — ${tiers[tier].name}`; break;
}
case 'unlock':
if (args.length) return usage();
seen.fill(1); player.lens = player.flask = true;
for (const m of MILESTONES) ledger[m.id] = ledger[m.id] || tick || 1;
result = 'All materials learned and progress rewards unlocked.'; break;
case 'tp': {
if (args.length !== 0 && (args.length !== 2 || !args.every(number))) return usage();
const x = args.length ? Number(args[0]) : cursor && cursor.x, y = args.length ? Number(args[1]) : cursor && cursor.y;
if (!Number.isFinite(x) || !Number.isFinite(y)) return ['No cursor position. Use tp <x> <y>.'];
bot.x = Math.max(0, Math.min(W - bot.w, x)); bot.y = Math.max(0, Math.min(H - bot.h, y)); bot.vx = bot.vy = 0;
fogLook(bot.x + bot.w / 2, bot.y + bot.h / 2);
result = `Bot: ${bot.x}, ${bot.y} grains`; break;
}
case 'free':
if (args.length !== 1 || !['on', 'off'].includes(args[0])) return usage();
devFree = args[0] === 'on'; result = 'Free building ' + args[0] + '.'; break;
case 'speed':
if (args.length !== 1 || !number(args[0])) return usage();
devSpeed = Math.max(0.1, Math.min(8, Number(args[0]))); result = 'Speed: ' + devSpeed + '×'; break;
case 'pause':
if (args.length) return usage();
devPaused = !devPaused; devSteps = 0; result = devPaused ? 'Dev paused. Use step or pause to resume.' : 'Dev pause off.'; break;
case 'step': {
const n = args.length ? Number(args[0]) : 1;
if (args.length > 1 || !Number.isInteger(n) || n < 1 || n > 10000 || devSteps + n > 10000) return usage();
if (!devPaused) return ['Use pause before step.'];
devSteps += n; result = `Queued ${n} ticks.`; break;
}
case 'weather':
if (args.length !== 1 || !['clear', 'rain'].includes(args[0])) return usage();
weather.shower = args[0] === 'rain' ? { x0: Math.max(0, bot.x - 160), x1: Math.min(W, bot.x + 160), rate: 2, ramp: 1, until: tick + 3600 } : null;
if (args[0] === 'clear') {
weather.raining.fill(0);
for (const f of flyers) if (f.rain) absorbSteam(f.x);
flyers = flyers.filter(f => !f.rain);
}
weather.next = tick + rainGap(); result = args[0] === 'rain' ? 'A one-minute shower started near the bot.' : 'Current rain cleared. Stored clouds may rain again.'; break;
default: return usage();
}
devLog.push([tick, text]); if (devLog.length > 200) devLog.shift();
return ['> ' + text, result];
}