Dev console: money, items, upgrades, free build, speed (t66)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-1
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"shell": "shell.html",
|
||||
"css": [
|
||||
"styles/style.css"
|
||||
"styles/style.css",
|
||||
"styles/devconsole.css"
|
||||
],
|
||||
"sim": [
|
||||
"sim/base.js",
|
||||
@@ -38,6 +39,7 @@
|
||||
"sim/tick.js",
|
||||
"sim/worldgen-helpers.js",
|
||||
"sim/worldgen.js",
|
||||
"sim/devconsole.js",
|
||||
"sim/save.js"
|
||||
],
|
||||
"client": [
|
||||
@@ -67,6 +69,7 @@
|
||||
"ui/selection-pickers.js",
|
||||
"ui/selection-panels.js",
|
||||
"ui/info.js",
|
||||
"ui/devconsole.js",
|
||||
"ui/input.js",
|
||||
"ui/actions.js",
|
||||
"ui/gamepad.js",
|
||||
|
||||
@@ -130,6 +130,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="dev-console" class="plate" aria-labelledby="dev-title" hidden>
|
||||
<div class="dev-head"><strong id="dev-title">Dev console</strong><span class="note">World marked · help lists commands</span><button id="dev-close" class="iconbtn" aria-label="Close dev console">Close <kbd>Esc</kbd></button></div>
|
||||
<div id="dev-output" role="log" aria-label="Command output" aria-live="polite"></div>
|
||||
<form id="dev-form"><label for="dev-input">Command</label><input id="dev-input" type="text" autocomplete="off" spellcheck="false" placeholder="help"><button class="btn" type="submit">Run</button></form>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
//__JS__
|
||||
</script>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#dev-console {
|
||||
position: fixed; z-index: 15; bottom: 0; left: 0; right: 0;
|
||||
padding: 12px 18px; border-radius: 0; border-top: 2px solid var(--patina);
|
||||
background: rgba(15, 13, 12, 0.97); max-height: 55vh;
|
||||
}
|
||||
.dev-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.dev-head strong { color: var(--patina); }
|
||||
.dev-head button { margin-left: auto; }
|
||||
#dev-output {
|
||||
height: 160px; max-height: 28vh; overflow-y: auto; margin: 8px 0;
|
||||
font: 12px/1.6 var(--font-mono); white-space: pre-wrap; overflow-wrap: anywhere;
|
||||
user-select: text;
|
||||
}
|
||||
#dev-form { display: flex; align-items: center; gap: 10px; }
|
||||
#dev-input { flex: 1; min-width: 0; font-family: var(--font-mono); }
|
||||
.dev-badge { display: inline-block; padding: 1px 6px; border: 1px solid var(--patina-dim); border-radius: 4px; color: var(--patina); font-size: 11px; }
|
||||
@@ -57,7 +57,7 @@ function buildVariantChips(P) {
|
||||
const dirChip = !P.dirs ? '' : dragging ? `<span class="chip ok">${glyph(slopeDir || P.dirs[GAME.dragDir])} ${esc(slopeDir || P.dirs[GAME.dragDir])} · from your drag</span>` : `<span class="chip hot">${glyph(slopeDir || dir)} ${esc(slopeDir || dir)} · R</span>`;
|
||||
const fc = GAME.filterCfg;
|
||||
const sws = ms => ms.slice(0, 6).map(m => `<span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:2px;background:${rgbOf(m)}"></span>`).join('') + (ms.length > 6 ? `+${ms.length - 6}` : '');
|
||||
return pieceUsesWall(P) ? `<button class="chip hot" data-wall-picker><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(WALL_OF[GAME.wallMat])}"></span>${esc(MD[WALL_OF[GAME.wallMat]].name.toLowerCase())} · R · F</button>${P.code === BP_WALL ? `<span class="chip hot">${['square', '◣ lower left', '◢ lower right', '◤ upper left', '◥ upper right'][isSlope(pieceCode(P)) ? pieceCode(P) - BP_SLOPE_BL + 1 : 0]} · T · turn / flip</span>` : ''}`
|
||||
return pieceUsesWall(P) ? `<button class="chip hot" data-wall-picker=""><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(WALL_OF[GAME.wallMat])}"></span>${esc(MD[WALL_OF[GAME.wallMat]].name.toLowerCase())} · R · F</button>${P.code === BP_WALL ? `<span class="chip hot">${['square', '◣ lower left', '◢ lower right', '◤ upper left', '◥ upper right'][isSlope(pieceCode(P)) ? pieceCode(P) - BP_SLOPE_BL + 1 : 0]} · T · turn / flip</span>` : ''}`
|
||||
: P.filter ? `<span class="chip hot">${fc.inv ? 'carries' : 'drops'} ${fc.m.length ? sws(fc.m) : 'nothing'} · F</span>${dirChip}`
|
||||
: dir ? dirChip : '';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Bottom panel; command dispatch and state live in sim/devconsole.js.
|
||||
function closeDevConsole() {
|
||||
$('dev-console').hidden = true;
|
||||
$('dev-input').blur();
|
||||
}
|
||||
function showDevConsole() {
|
||||
$('dev-console').hidden = false;
|
||||
if (!$('dev-output').children.length) devPrint(['Type help to list commands. Backtick or Escape closes this panel.']);
|
||||
$('dev-input').focus();
|
||||
}
|
||||
function toggleDevConsole() {
|
||||
if (!GAME.running || GAME.loading) return;
|
||||
if (!$('dev-console').hidden) { closeDevConsole(); return; }
|
||||
if (devWorld) { showDevConsole(); return; }
|
||||
dialog('Enable dev console?', '<p>This save will be permanently marked as a dev-console world. If achievements are added later, they will not unlock in this world.</p><p>You can change money, materials, upgrades and game speed. These commands are logged but cannot be undone.</p>', [
|
||||
{ label: 'Cancel' },
|
||||
{ label: 'Enable dev console', cls: 'primary', fn: () => {
|
||||
enableDevConsole(); closeModal('m-dialog'); showDevConsole(); saveWorld('main'); return true;
|
||||
} },
|
||||
]);
|
||||
}
|
||||
function devPrint(lines) {
|
||||
const log = $('dev-output');
|
||||
for (const line of lines) { const row = document.createElement('div'); row.textContent = line; log.appendChild(row); }
|
||||
while (log.children.length > 400) log.firstChild.remove();
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
function runDevCommand(text) {
|
||||
devPrint(devCommand(text, GAME.aim));
|
||||
hudDirty = true; lastBagKey = ''; lastBuildKey = '';
|
||||
renderWallet();
|
||||
if (!$('m-notebook').hidden) renderNotebook();
|
||||
}
|
||||
$('dev-close').onclick = closeDevConsole;
|
||||
$('dev-input').onfocus = () => { keys.clear(); GAME.lmb = GAME.rmb = false; GAME.drag = null; GAME.replace = false; };
|
||||
$('dev-input').onkeydown = e => {
|
||||
if (e.key === 'Escape' || e.key === '`') { e.preventDefault(); e.stopPropagation(); closeDevConsole(); }
|
||||
};
|
||||
$('dev-form').onsubmit = e => {
|
||||
e.preventDefault();
|
||||
const input = $('dev-input'); runDevCommand(input.value); input.value = ''; input.focus();
|
||||
};
|
||||
+2
-1
@@ -24,6 +24,7 @@ window.addEventListener('keydown', e => {
|
||||
if (e.repeat) return;
|
||||
GAME.usingPad = false;
|
||||
switch (k) {
|
||||
case '`': e.preventDefault(); toggleDevConsole(); break;
|
||||
case '1': case '2': case '3': case '4': selectTool(+k - 1); break;
|
||||
case 'c': case 'x': hudDirty = true; break;
|
||||
case 'v': startPaste(); break;
|
||||
@@ -42,7 +43,7 @@ window.addEventListener('keydown', e => {
|
||||
case '[': setBrush(-1); break;
|
||||
case ']': setBrush(1); break;
|
||||
case 'Delete': if (ghostBlocks.size || ghostMachines.length || ghostMolds.length) { clearAllGhosts(); toast('Blueprint ghosts cleared.'); } break;
|
||||
case 'Escape': if (GAME.selDrag || GAME.paste) stopSelectAndPaste(); else openPause(); break;
|
||||
case 'Escape': if (!$('dev-console').hidden) closeDevConsole(); else if (GAME.selDrag || GAME.paste) stopSelectAndPaste(); else openPause(); break;
|
||||
case 'Tab': $('bagpanel').hidden = !$('bagpanel').hidden; break;
|
||||
}
|
||||
});
|
||||
|
||||
+5
-4
@@ -22,11 +22,11 @@ function frame(now) {
|
||||
const pad = pollPad();
|
||||
if (GAME.usingPad) { GAME.aim.x = bot.x + bot.w / 2 + GAME.padAim.x; GAME.aim.y = bot.y + bot.h / 2 + GAME.padAim.y; }
|
||||
else { const w = screenToWorld(GAME.mouse.x, GAME.mouse.y); GAME.aim.x = w.x; GAME.aim.y = w.y; }
|
||||
if (!GAME.paused) {
|
||||
acc += dtReal * SET.simSpeed;
|
||||
if (!GAME.paused && (!devPaused || devSteps > 0)) {
|
||||
acc += devPaused ? 0 : dtReal * SET.simSpeed * devSpeed;
|
||||
let steps = 0;
|
||||
const step = 1 / 60;
|
||||
while (acc >= step && steps < SET.maxSteps) {
|
||||
while ((devPaused ? devSteps > 0 : acc >= step) && steps < SET.maxSteps) {
|
||||
let ix = 0, iy = 0;
|
||||
if (keys.has('a') || keys.has('ArrowLeft')) ix -= 1; if (keys.has('d') || keys.has('ArrowRight')) ix += 1;
|
||||
if (keys.has('w') || keys.has('ArrowUp') || keys.has(' ')) iy -= 1; if (keys.has('s') || keys.has('ArrowDown')) iy += 1;
|
||||
@@ -38,7 +38,8 @@ function frame(now) {
|
||||
botStep(ix, iy, keys.has('Shift') || GAME.padRun > 24);
|
||||
toolTick();
|
||||
const s0 = performance.now(); simTick(); simMs = simMs * 0.9 + (performance.now() - s0) * 0.1;
|
||||
acc -= step; steps++;
|
||||
if (devPaused) devSteps--; else acc -= step;
|
||||
steps++;
|
||||
GAME.playTime += step;
|
||||
}
|
||||
if (steps >= SET.maxSteps) acc = 0;
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ function renderNotebook() {
|
||||
}
|
||||
function controlsHtml() {
|
||||
const kb = [
|
||||
['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
|
||||
['Dev console (marks the world)', 'Backtick (`)'], ['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
|
||||
['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall: cycle shape · flip diagonal drag', 'T'], ['Wall or mold material menu (Build)', 'F'],
|
||||
['Pick up selection or what you click (Pneumatic nozzle)', 'Left drag · Shift: everything'], ['Select (Pneumatic nozzle)', 'Click a bag name · Q/E'], ['Add to selection (Pneumatic nozzle)', 'Ctrl+click a bag name'], ['Show everything found (Pneumatic nozzle)', 'Hold Ctrl in the bag'], ['Clear selection (Pneumatic nozzle)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
|
||||
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and screw conveyor directions', 'I'], ['Bot lamp', 'L'],
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ async function saveWorld(slot, quiet) {
|
||||
const list = await listWorlds();
|
||||
let entry = list.find(w => w.id === id);
|
||||
if (!entry) { entry = { id, name, createdAt: savedAt }; list.push(entry); }
|
||||
Object.assign(entry, { savedAt, playTime, seed: s.meta.seed, w: s.meta.W, h: s.meta.H, debt: s.meta.economy.debt, ...(c ? { career: c.id, claimNo } : {}) });
|
||||
Object.assign(entry, { savedAt, playTime, seed: s.meta.seed, w: s.meta.W, h: s.meta.H, debt: s.meta.economy.debt, dev: s.meta.dev, ...(c ? { career: c.id, claimNo } : {}) });
|
||||
await DB.op('readwrite', st => { st.put({ meta: s.meta, buffers, savedAt, playTime }, `${id}:${slot}`); st.put(list, 'worlds'); if (c) st.put(c, 'career:' + c.id); });
|
||||
});
|
||||
if (!quiet) toast(slot === 'auto' ? 'Autosaved.' : 'World saved.');
|
||||
|
||||
@@ -91,6 +91,7 @@ function pieceExtra(P) { return P.filter ? { m: GAME.filterCfg.m.slice(), inv: G
|
||||
function filterBlockAt(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return -1; const b = ((y / BS) | 0) * BW + ((x / BS) | 0); return isFilter(blockP[b]) ? b : -1; }
|
||||
function chipCost(cost) { const ok = canAfford(cost, 1); return `<span class="chip ${ok ? 'ok' : 'bad'}">${esc(costText(cost, 1))}</span>`; }
|
||||
function pieceCostNow(P, m = GAME.wallMat) {
|
||||
if (devFree) return [[CASH, 0]];
|
||||
if (P.kind === 'mold') return wallCost(m, MOLDS[P.mold].walls);
|
||||
if (P.kind === 'block' && isWall(P.code)) return pieceCost(pieceCode(P), m);
|
||||
return P.cost;
|
||||
|
||||
+3
-2
@@ -11,6 +11,7 @@ function showLoading(on, p, label, title) {
|
||||
if (on) { $('loadbar').style.width = Math.round((p || 0) * 100) + '%'; $('loadtext').textContent = Math.round((p || 0) * 100) + '%'; }
|
||||
}
|
||||
function enterGame() {
|
||||
closeDevConsole(); $('dev-output').textContent = ''; $('dev-input').value = '';
|
||||
$('cards').innerHTML = '';
|
||||
if (R.floaters) R.floaters.length = 0; R.particles.length = 0;
|
||||
GAME.status = ''; GAME.statusT = 0;
|
||||
@@ -80,7 +81,7 @@ async function loadSavedWorld(id) {
|
||||
}
|
||||
async function quitToTitle() {
|
||||
await saveWorld('main', true);
|
||||
GAME.running = false; closeAllModals();
|
||||
GAME.running = false; closeDevConsole(); closeAllModals();
|
||||
$('hud').hidden = true; $('title').hidden = false;
|
||||
refreshContinue();
|
||||
if (!TITLE.raf) drawTitle();
|
||||
@@ -135,7 +136,7 @@ async function renderWorlds(editId) {
|
||||
for (const w of list) {
|
||||
const row = el('div', { class: 'world-row', 'data-world': w.id });
|
||||
const mins = Math.floor((w.playTime || 0) / 60);
|
||||
row.innerHTML = `<div class="world-info"><strong>${esc(w.name)}</strong><div class="note">seed ${esc(w.seed)} · ${esc(w.w)}×${esc(w.h)} grains · ${Math.floor(mins / 60)}:${String(mins % 60).padStart(2, '0')} played</div><div class="note">${w.savedAt ? esc(new Date(w.savedAt).toLocaleString()) : 'Not saved yet'}</div></div>`;
|
||||
row.innerHTML = `<div class="world-info"><strong>${esc(w.name)}</strong>${w.dev ? ' <span class="dev-badge">Dev console</span>' : ''}<div class="note">seed ${esc(w.seed)} · ${esc(w.w)}×${esc(w.h)} grains · ${Math.floor(mins / 60)}:${String(mins % 60).padStart(2, '0')} played</div><div class="note">${w.savedAt ? esc(new Date(w.savedAt).toLocaleString()) : 'Not saved yet'}</div></div>`;
|
||||
const actions = el('div', { class: 'world-actions' });
|
||||
const button = (label, fn, cls = '') => { const b = el('button', { class: 'btn ' + cls }, label); b.onclick = async () => { try { await fn(); } catch (e) { toast('Could not reach browser storage.'); } }; actions.appendChild(b); };
|
||||
if (editId === w.id) {
|
||||
|
||||
Reference in New Issue
Block a user