Dev console: money, items, upgrades, free build, speed (t66)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// 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) return ['Air cannot go in the bag.'];
|
||||
if (n > 2147483647 - bag[m]) return ['Too many: a material count cannot exceed 2147483647.'];
|
||||
bagAdd(m, n, true); result = `Added ${n} ${MD[m].name}.`; 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];
|
||||
}
|
||||
+2
-2
@@ -310,8 +310,8 @@ function giveOrDrop(m, n, x, y) {
|
||||
if (rest > 0) bagAdd(m, rest, true);
|
||||
}
|
||||
function refundAt(cost, mult, x, y) { for (const [m, n] of cost) giveOrDrop(m, n * mult, x, y); }
|
||||
function canAfford(cost, mult) { for (const [m, n] of cost) if ((m === CASH ? money : bag[m]) < n * mult) return false; return true; }
|
||||
function payCost(cost, mult) { for (const [m, n] of cost) { if (m === CASH) money -= n * mult; else bagTake(m, n * mult); } }
|
||||
function canAfford(cost, mult) { if (devFree) return true; for (const [m, n] of cost) if ((m === CASH ? money : bag[m]) < n * mult) return false; return true; }
|
||||
function payCost(cost, mult) { if (devFree) return; for (const [m, n] of cost) { if (m === CASH) money -= n * mult; else bagTake(m, n * mult); } }
|
||||
function fmtMoney(v) { return '$' + Math.round(v).toLocaleString('en-US'); }
|
||||
function costText(cost, mult) { return cost.map(([m, n]) => { const k = n * (mult || 1); return m === CASH ? fmtMoney(k) : `${k} ${MD[m].name.toLowerCase()}${PH[m] === PH_ITEM && k !== 1 ? 's' : ''}`; }).join(' + '); }
|
||||
function needText(cost, mult) { const c = cost.find(([m, n]) => (m === CASH ? money : bag[m]) < n * (mult || 1)); return c && c[0] === CASH ? `Costs ${fmtMoney(c[1] * (mult || 1))}; you have ${fmtMoney(money)}.` : 'Not enough ' + costText(c ? [c] : cost, mult) + '.'; }
|
||||
|
||||
@@ -9,6 +9,7 @@ function serializeWorld() {
|
||||
const t16 = new Uint16Array(N);
|
||||
for (let i = 0; i < N; i++) { const v = (temp[i] + TOFF) * 16; t16[i] = v < 0 ? 0 : v > 65535 ? 65535 : v; }
|
||||
const meta = {
|
||||
dev: devWorld, devLog: devLog.map(e => e.slice()),
|
||||
slopePhases: Array.from(slopePhase),
|
||||
v: SAVE_VERSION, seed: worldSeed, W, H, tick, surfBase, underD, shallowD, lapse, tOff: TOFF,
|
||||
saplings: saplings.map(s => [s.x, s.y, s.age, s.th, s.seed]),
|
||||
@@ -32,6 +33,8 @@ function serializeWorld() {
|
||||
function deserializeWorld(meta, buffers) {
|
||||
if (meta.v !== SAVE_VERSION) throw new Error('This world was saved by an older version of the game and cannot be loaded.');
|
||||
alloc(meta.W, meta.H);
|
||||
devWorld = meta.dev === true;
|
||||
devLog = devWorld && Array.isArray(meta.devLog) ? meta.devLog.filter(e => Array.isArray(e) && Number.isSafeInteger(e[0]) && e[0] >= 0 && typeof e[1] === 'string').slice(-200).map(e => [e[0], e[1]]) : [];
|
||||
worldSeed = meta.seed;
|
||||
if (meta.surfBase) { surfBase = meta.surfBase; underD = meta.underD; }
|
||||
shallowD = meta.shallowD || 0; lapse = meta.lapse || 0.36;
|
||||
|
||||
@@ -39,6 +39,7 @@ function discover(m, x, y, from, how, T) {
|
||||
}
|
||||
|
||||
function alloc(w, h) {
|
||||
resetDevConsole();
|
||||
W = w; H = h; N = w * h;
|
||||
fogAlloc();
|
||||
CW = Math.ceil(w / 32); CH = Math.ceil(h / 32); NCH = CW * CH;
|
||||
|
||||
Reference in New Issue
Block a user