Vacuum menu on F replaces the pick-up modes (t16)

Pick-up takes everything unless something is marked; F with the Vacuum
(when nothing in reach can be used) or the card chips open a menu with
pick-up and blow toggles per material. The bag rows lose their buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 02:28:56 -04:00
co-authored by Claude Opus 5.5
parent 255824ec6d
commit aa46406c92
16 changed files with 148 additions and 125 deletions
+34 -27
View File
@@ -53,23 +53,10 @@ function rotate() {
else return;
playSfx('ui'); hudDirty = true;
}
const PICK_MODES = ['first', 'marked', 'all'];
const PICK_LABELS = { first: 'What I click', marked: 'Only marked', all: 'Everything' };
function setPickMode(mode) {
GAME.pickMode = mode;
try { localStorage.setItem('deepgrain.pickMode', mode); } catch (e) { /* storage unavailable */ }
hudDirty = true;
}
function cyclePickMode() {
setPickMode(PICK_MODES[(PICK_MODES.indexOf(GAME.pickMode) + 1) % PICK_MODES.length]);
playSfx('ui');
}
function toggleCollect(m) {
if (!m) return;
const add = !GAME.collect.has(m);
if (add) GAME.collect.add(m); else GAME.collect.delete(m);
if (add && GAME.pickMode !== 'marked') { setPickMode('marked'); setStatus('Picking up only marked materials.', 'info'); }
else if (!GAME.collect.size && GAME.pickMode === 'marked') setPickMode('first');
try { localStorage.setItem('deepgrain.collect', JSON.stringify([...GAME.collect])); } catch (e) { /* storage unavailable */ }
lastBagKey = ''; hudDirty = true; playSfx('ui');
}
@@ -154,6 +141,34 @@ function openFilterPicker(cur, onDone, title) {
box.querySelectorAll('[data-m]').forEach(b => b.onclick = () => { const m = +b.dataset.m; if (sel.has(m)) sel.delete(m); else sel.add(m); playSfx('ui'); sync(); });
sync();
}
function openVacuumMenu() {
ensureBlow();
dialog('Vacuum', '', [
{ label: 'Pick up everything', cls: 'ghost', fn: () => {
GAME.collect.clear();
try { localStorage.setItem('deepgrain.collect', '[]'); } catch (e) { /* storage unavailable */ }
lastBagKey = ''; hudDirty = true; playSfx('ui'); sync(); return true;
} },
{ label: 'Blow one at a time', cls: 'ghost', fn: () => {
GAME.blow = new Set(blowList().slice(0, 1));
lastBagKey = ''; hudDirty = true; playSfx('ui'); sync(); return true;
} },
{ label: 'Done', cls: 'primary' },
]);
const box = $('dlg-body');
function sync() {
const list = BAG_ORDER.filter(m => bag[m] > 0)
.concat(BAG_ORDER.filter(m => !bag[m] && (GAME.collect.has(m) || GAME.blow.has(m))))
.concat(BAG_ORDER.filter(m => !bag[m] && !GAME.collect.has(m) && !GAME.blow.has(m) && seen[m] && filterable(m)));
const blow = blowList();
box.innerHTML = `<p class="note" id="vm-sum">${esc(GAME.collect.size ? 'Picking up only ' + matList([...GAME.collect]) + '.' : 'Picking up everything.')} Blowing out ${blow.length > 1 ? `an even mix of ${blow.length}` : blow.length ? esc(MD[blow[0]].name.toLowerCase()) : 'nothing'}.</p>`
+ (!GAME.collect.size ? '<p class="note">Mark materials to pick up only those.</p>' : '')
+ (list.length ? `<div class="vpick">${list.map(m => `<div class="wp"><span class="sw" style="background:${rgbOf(m)}"></span><span><b>${esc(MD[m].name)}</b><span>${bag[m] ? `you have ${bag[m].toLocaleString('en-US')}` : 'none in bag'}</span><div class="vtoggles"><button data-vm-pick="${m}" aria-pressed="${GAME.collect.has(m)}" class="btn ${GAME.collect.has(m) ? 'on' : ''}">⬇ Pick up</button><button data-vm-blow="${m}" aria-pressed="${GAME.blow.has(m)}" class="btn ${GAME.blow.has(m) ? 'on' : ''}"${!bag[m] && !GAME.blow.has(m) ? ' disabled' : ''}>⬆ Blow</button></div></span></div>`).join('')}</div>` : '<p class="note">Find something loose first.</p>');
box.querySelectorAll('[data-vm-pick]').forEach(b => b.onclick = () => { toggleCollect(+b.dataset.vmPick); sync(); });
box.querySelectorAll('[data-vm-blow]').forEach(b => b.onclick = () => { toggleBlow(+b.dataset.vmBlow); sync(); });
}
sync();
}
function openWallPicker() {
const P = piece(), title = P.kind === 'mold' ? `${P.name} material` : 'Wall material';
const list = WALL_LIST.filter(m => bag[m] > 0).concat(WALL_LIST.filter(m => !bag[m]));
@@ -211,13 +226,12 @@ function renderSelCard() {
+ '<div class="sub"><kbd>Q</kbd>/<kbd>E</kbd> piece · <kbd>Shift</kbd>+<kbd>Q</kbd>/<kbd>E</kbd> tab · <kbd>R</kbd> turn or material · <kbd>F</kbd> material menu · hold <kbd>G</kbd> replace · right click take back · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo</div>';
} else if (t === 'vacuum') {
ensureBlow();
const list = blowList(), f = GAME.lmb && GAME.pickMode === 'first' ? GAME.vacFilter : 0;
const marks = GAME.pickMode === 'marked' ? [...GAME.collect] : f > 0 ? [f] : [];
const list = blowList(), marks = [...GAME.collect];
const sws = ms => ms.slice(0, 4).map(m => `<span class="sw" title="${esc(MD[m].name)}" style="background:${rgbOf(m)}"></span>`).join('') + (ms.length > 4 ? `+${ms.length - 4}` : '');
const count = list.reduce((n, m) => n + bag[m], 0);
h = `<div class="row vacrow"><b>Pick up</b><button class="chip hot" data-pick-mode title="Cycle pick-up mode">${PICK_LABELS[GAME.pickMode]} · T</button>${sws(marks)}</div>
<div class="row vacrow"><b>Blow out</b><kbd>Q</kbd>${sws(list)}<span class="name grow">${list.length > 1 ? `Mix of ${list.length}` : list.length ? esc(MD[list[0]].name) : 'bag empty'}</span><span class="num chip${count ? '' : ' bad'}">${count ? count.toLocaleString('en-US') : 'none left'}</span><kbd>E</kbd></div>
<div class="sub">Left picks up, right blows out. R marks what is under the cursor. Shift takes everything. <span class="chip">nozzle ${GAME.brush[1]}</span></div>`;
h = `<div class="row vacrow"><b>Pick up</b><button class="chip hot" data-vac-menu>${marks.length ? 'Only marked' : 'Everything'} · F</button>${sws(marks)}</div>
<div class="row vacrow"><b>Blow out</b><kbd>Q</kbd><button class="name grow vacchoice" data-vac-menu>${sws(list)}${list.length > 1 ? `Mix of ${list.length}` : list.length ? esc(MD[list[0]].name) : 'bag empty'}</button><span class="num chip${count ? '' : ' bad'}">${count ? count.toLocaleString('en-US') : 'none left'}</span><kbd>E</kbd></div>
<div class="sub">Left picks up, right blows out. F chooses what. R marks what is under the cursor. Shift takes everything. <span class="chip">nozzle ${GAME.brush[1]}</span></div>`;
} else if (t === 'ignite') {
h = `<div class="row"><span class="name">Igniter</span><span class="chip hot">up to 640°C</span></div><div class="sub">Hold on fuel to light it. Coal catches around 420°C, wood around 300°C. Fuel needs open air next to it.</div>`;
}
@@ -240,10 +254,7 @@ function renderBag() {
for (const m of l) {
const sel = (t === 'vacuum' && GAME.blow.has(m)) || (t === 'build' && m === GAME.wallMat && pieceUsesWall(piece())) || (t === 'build' && piece().filter && GAME.filterCfg.m.includes(m));
const row = el('div', { class: 'bagrow' + (sel ? ' on' : '') });
if (t === 'vacuum') {
const down = el('button', { class: 'mark' + (GAME.collect.has(m) ? ' on' : ''), title: `Pick up ${MD[m].name}`, 'aria-label': `Pick up ${MD[m].name}`, 'aria-pressed': GAME.collect.has(m), 'data-pick': m }, '⬇');
down.onclick = () => toggleCollect(m); row.appendChild(down);
}
if (t === 'vacuum' && GAME.collect.has(m)) row.appendChild(el('span', { class: 'tag', title: 'Marked to pick up', 'aria-label': 'Marked to pick up' }, '⬇'));
const b = el('button', { class: 'pick', title: WALL_OF[m] && t === 'build' ? `Build with ${MD[m].name}` : `Blow out ${MD[m].name}` });
b.innerHTML = `<span class="sw" style="background:${rgbOf(m)}"></span><span class="nm">${esc(MD[m].name)}</span><span class="n num">${bag[m].toLocaleString('en-US')}</span>`;
b.onclick = () => {
@@ -253,11 +264,7 @@ function renderBag() {
hudDirty = true; lastBagKey = ''; playSfx('ui');
};
row.appendChild(b);
if (t === 'vacuum') {
const up = el('button', { class: 'mark' + (GAME.blow.has(m) ? ' on' : ''), title: `Blow out ${MD[m].name}`, 'aria-label': `Blow out ${MD[m].name}`, 'aria-pressed': GAME.blow.has(m), 'data-blow': m }, '⬆');
up.onclick = () => toggleBlow(m); row.appendChild(up);
}
box.appendChild(row);
}
if (t === 'vacuum') box.appendChild(el('div', { class: 'baghint' }, '⬇ what you pick up · ⬆ what you blow out · click a name to blow only that'));
if (t === 'vacuum') box.appendChild(el('div', { class: 'baghint' }, 'F with the Vacuum: choose what to pick up and blow'));
}