Vacuum: one selection for pick-up and blow, full list while Ctrl is held (t20)
With anything selected a drag takes only the selection; with nothing selected it takes what you click. Right click blows the selected materials in the bag. Holding Ctrl lists every material found so far under "Not in bag". Selections stay when materials run out; Clear button, clicking the only selected row, or gamepad B clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -243,6 +243,13 @@ above, including:
|
|||||||
the hand (this settles the old t18 question), and an empty hand blows nothing and says so.
|
the hand (this settles the old t18 question), and an empty hand blows nothing and says so.
|
||||||
Picking something up fills an empty hand, never a chosen one. One tool, no menu; R and F do
|
Picking something up fills an empty hand, never a chosen one. One tool, no menu; R and F do
|
||||||
nothing special with the Vacuum. The owner mostly uses it to collect ore and fuel and feed furnaces.
|
nothing special with the Vacuum. The owner mostly uses it to collect ore and fuel and feed furnaces.
|
||||||
|
Follow-up the same day: the hand became **one selection for both** picking up and blowing out,
|
||||||
|
because there was no way to choose several materials to pick up, or ones not in the bag yet. With
|
||||||
|
anything selected, a drag takes only the selection wherever it starts; with nothing selected it
|
||||||
|
takes what you click. Holding Ctrl lists every material found so far below a "Not in bag" divider
|
||||||
|
(no menu). Selected materials stay selected when they run out, and picking up never selects
|
||||||
|
anything (both reverse the first t20 version). Clear: a button in the bag, clicking the only
|
||||||
|
selected row, or gamepad B.
|
||||||
|
|
||||||
- **Fog of war also reveals the ground around the bot (t17, 2026-10-24).** About 20 grains in every
|
- **Fog of war also reveals the ground around the bot (t17, 2026-10-24).** About 20 grains in every
|
||||||
direction, through rock, fully clear with a soft edge. Line of sight alone hid everything inside
|
direction, through rock, fully clear with a soft edge. Line of sight alone hid everything inside
|
||||||
|
|||||||
+3
-3
@@ -366,7 +366,7 @@ Audio: chiptune effects and generative music that changes with depth. Everything
|
|||||||
- functions: auInit, auVolumes, rateOk, tone, noiseBurst, sfxPlace, playSfx, auAmbient, mtof, zoneForDepth, scaleNote, musicScheduler
|
- functions: auInit, auVolumes, rateOk, tone, noiseBurst, sfxPlace, playSfx, auAmbient, mtof, zoneForDepth, scaleNote, musicScheduler
|
||||||
- variables: AU, SFX, SFX_NEAR, SFX_FAR, ZONES
|
- variables: AU, SFX, SFX_NEAR, SFX_FAR, ZONES
|
||||||
|
|
||||||
### src/ui/state.js (66 lines)
|
### src/ui/state.js (67 lines)
|
||||||
Game shell (src/ui/): browser only. This file: settings, the GAME state object, DOM helpers, toasts and the status line.
|
Game shell (src/ui/): browser only. This file: settings, the GAME state object, DOM helpers, toasts and the status line.
|
||||||
- functions: loadSettings, saveSettings, el, esc, rgbOf, fmtT, toast, setStatus
|
- functions: loadSettings, saveSettings, el, esc, rgbOf, fmtT, toast, setStatus
|
||||||
- variables: SET_DEFAULT, SET, TOOLS, ICONS, BAG_ORDER, GAME, keys, hudDirty, $
|
- variables: SET_DEFAULT, SET, TOOLS, ICONS, BAG_ORDER, GAME, keys, hudDirty, $
|
||||||
@@ -404,7 +404,7 @@ The build menu along the bottom: categories, piece tiles and their icons.
|
|||||||
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
|
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
|
||||||
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
|
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
|
||||||
|
|
||||||
### src/ui/selection.js (230 lines)
|
### src/ui/selection.js (241 lines)
|
||||||
The selected tool and piece: R / Q / E, drill width, filter picker, the selection card, the bag panel.
|
The selected tool and piece: R / Q / E, drill width, filter picker, the selection card, the bag panel.
|
||||||
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
||||||
- variables: lastBagKey
|
- variables: lastBagKey
|
||||||
@@ -413,7 +413,7 @@ The selected tool and piece: R / Q / E, drill width, filter picker, the selectio
|
|||||||
Discovery cards, hover info (I) and the cursor label.
|
Discovery cards, hover info (I) and the cursor label.
|
||||||
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel
|
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel
|
||||||
|
|
||||||
### src/ui/input.js (79 lines)
|
### src/ui/input.js (80 lines)
|
||||||
Keyboard and mouse listeners.
|
Keyboard and mouse listeners.
|
||||||
- functions: isTyping
|
- functions: isTyping
|
||||||
- variables: canvas
|
- variables: canvas
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-24: t20 follow-up: one selection, full list while Ctrl is held (Claude Code, Codex)
|
||||||
|
|
||||||
|
- `GAME.blow` is now the selection for picking up and blowing out (`vacFilter` -2 = the selection).
|
||||||
|
`blowList()` returns only selected materials in the bag; no pruning, no auto-select. `GAME.ctrl`
|
||||||
|
(input.js) makes `renderBag` add a "Not in bag" section of every `seen` material the vacuum can
|
||||||
|
take; selected but empty materials always show there. Clear button, click-the-only-row, gamepad B.
|
||||||
|
- Files: `src/ui/{actions,selection,hud,input,state,gamepad,previews,notebook,session}.js`,
|
||||||
|
`src/sim/{vacuum,progress}.js`, `src/styles/style.css`, `test/play.py`, `test/t_basics.js`.
|
||||||
|
- Tests: `run_all.py --browser` passed (play.py 226) except `t_smooth` half-resolution at load 8.4,
|
||||||
|
which passed on rerun. Screenshots `shots/t20b-{noctrl,ctrl,ctrl-900}.png`.
|
||||||
|
|
||||||
## 2026-10-24: t20: pick up what you click, blow out what's in hand (Claude Code, Codex)
|
## 2026-10-24: t20: pick up what you click, blow out what's in hand (Claude Code, Codex)
|
||||||
|
|
||||||
- After discussing options with the owner: one Vacuum, a drag locks onto the material it starts on
|
- After discussing options with the owner: one Vacuum, a drag locks onto the material it starts on
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ const STARTER_DESIGNS = [
|
|||||||
|
|
||||||
// ---------- Milestones ----------
|
// ---------- Milestones ----------
|
||||||
const MILESTONES = [
|
const MILESTONES = [
|
||||||
{ id: 'dig', title: 'Break ground', msg: 'Broken material falls loose. The Vacuum (2) picks up what you click. Click or Q/E in the bag chooses the hand; Ctrl+click mixes, Shift takes or blows everything.', hint: 'Hold the left mouse button with the Drill (1): it sends pulses at the cursor that shake rock until it shatters. It falls as rubble, ore and coal.' },
|
{ id: 'dig', title: 'Break ground', msg: 'Broken material falls loose. The Vacuum (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', hint: 'Hold the left mouse button with the Drill (1): it sends pulses at the cursor that shake rock until it shatters. It falls as rubble, ore and coal.' },
|
||||||
{ id: 'fire', title: 'First fire', hint: 'Vacuum up coal or wood chunks, blow a pile out with right click, then hold the Igniter (4) on it. Fuel only burns where it touches open air.', reward: 'Thermal lens unlocked. Press H to see heat.' },
|
{ id: 'fire', title: 'First fire', hint: 'Vacuum up coal or wood chunks, blow a pile out with right click, then hold the Igniter (4) on it. Fuel only burns where it touches open air.', reward: 'Thermal lens unlocked. Press H to see heat.' },
|
||||||
{ id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and vacuum up the brick rubble. Brick rubble builds brick blocks.', reward: 'Brick insulates far better than stone and survives 1750°C.' },
|
{ id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and vacuum up the brick rubble. Brick rubble builds brick blocks.', reward: 'Brick insulates far better than stone and survives 1750°C.' },
|
||||||
{ id: 'charcoal', title: 'Charcoal', hint: 'Wood that gets hot without air turns into charcoal instead of burning. Bury a burning pile of wood chunks under dirt, or heat a sealed box of wood chunks from outside.', reward: 'A fuel you can grow: nearly as hot as coal.' },
|
{ id: 'charcoal', title: 'Charcoal', hint: 'Wood that gets hot without air turns into charcoal instead of burning. Bury a burning pile of wood chunks under dirt, or heat a sealed box of wood chunks from outside.', reward: 'A fuel you can grow: nearly as hot as coal.' },
|
||||||
|
|||||||
+3
-3
@@ -46,12 +46,12 @@ function toolBlow(cx, cy, r, m, mix) {
|
|||||||
let pickM = null;
|
let pickM = null;
|
||||||
if (mix && mix.length) {
|
if (mix && mix.length) {
|
||||||
const avail = mix.filter(k => bag[k] > 0 && PH[k] !== PH_ITEM && (PH[k] !== PH_LIQUID || player.flask));
|
const avail = mix.filter(k => bag[k] > 0 && PH[k] !== PH_ITEM && (PH[k] !== PH_LIQUID || player.flask));
|
||||||
if (!avail.length) return { placed: 0, status: 'Nothing in hand: Q/E or click something in the bag.' };
|
if (!avail.length) return { placed: 0, status: 'None of the selected materials are in the bag.' };
|
||||||
pickM = () => { for (let k = 0; k < avail.length; k++) { const c = avail[(blowRR++) % avail.length]; if (bag[c] > 0) return c; } return 0; };
|
pickM = () => { for (let k = 0; k < avail.length; k++) { const c = avail[(blowRR++) % avail.length]; if (bag[c] > 0) return c; } return 0; };
|
||||||
m = avail[0];
|
m = avail[0];
|
||||||
}
|
}
|
||||||
if (!m) return { placed: 0, status: 'Nothing in hand: Q/E or click something in the bag.' };
|
if (!m) return { placed: 0, status: 'Nothing selected: click or Q/E in the bag.' };
|
||||||
if (bag[m] <= 0) return { placed: 0, status: `No ${MD[m].name.toLowerCase()} left.` };
|
if (bag[m] <= 0) return { placed: 0, status: 'None of the selected materials are in the bag.' };
|
||||||
const ph = PH[m];
|
const ph = PH[m];
|
||||||
if (ph === PH_LIQUID && !player.flask) return { placed: 0, status: 'You need the Flask to carry liquids.' };
|
if (ph === PH_LIQUID && !player.flask) return { placed: 0, status: 'You need the Flask to carry liquids.' };
|
||||||
const nz = nozzle(cx + 0.5, cy + 0.5);
|
const nz = nozzle(cx + 0.5, cy + 0.5);
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ table.mt td.dip { color: var(--melt); }
|
|||||||
#selcard .vacrow { flex-wrap: wrap; gap: 4px; }
|
#selcard .vacrow { flex-wrap: wrap; gap: 4px; }
|
||||||
#selcard .vacrow .sw { width: 9px; height: 9px; }
|
#selcard .vacrow .sw { width: 9px; height: 9px; }
|
||||||
#baglist .baghint { color: var(--ash-dim); font-size: 0.82em; padding: 4px 4px 0; }
|
#baglist .baghint { color: var(--ash-dim); font-size: 0.82em; padding: 4px 4px 0; }
|
||||||
|
.bagsep { margin: 6px 4px 2px; padding-top: 5px; border-top: 1px solid var(--ash-dim); color: var(--ash-dim); font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.08em; }
|
||||||
|
#baglist .bagrow.none { opacity: 0.55; }
|
||||||
|
#baglist .bagrow.none:hover { opacity: 1; }
|
||||||
|
#baglist .bagclear { display: inline-flex; margin-left: 5px; padding: 1px 5px; background: transparent; color: var(--ash-dim); border: 1px solid var(--soot-3); font-size: inherit; }
|
||||||
#bagtoggle { background: none; border: none; color: var(--ash); cursor: pointer; padding: 0 2px; }
|
#bagtoggle { background: none; border: none; color: var(--ash); cursor: pointer; padding: 0 2px; }
|
||||||
|
|
||||||
#info { padding: 7px 10px; min-width: calc(220px * var(--ui)); max-width: min(360px, 60vw); display: flex; flex-direction: column; gap: 2px; font-size: 0.92em; }
|
#info { padding: 7px 10px; min-width: calc(220px * var(--ui)); max-width: min(360px, 60vw); display: flex; flex-direction: column; gap: 2px; font-size: 0.92em; }
|
||||||
|
|||||||
+5
-5
@@ -118,7 +118,7 @@ function primaryPress(shift) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t === 'vacuum') {
|
if (t === 'vacuum') {
|
||||||
GAME.vacFilter = shift ? -1 : grainAt(ax, ay);
|
GAME.vacFilter = shift ? -1 : GAME.blow.size ? -2 : grainAt(ax, ay);
|
||||||
hudDirty = true;
|
hudDirty = true;
|
||||||
} else if (t === 'build') {
|
} else if (t === 'build') {
|
||||||
const P = piece();
|
const P = piece();
|
||||||
@@ -207,8 +207,8 @@ function toolTick() {
|
|||||||
if (GAME.vacFilter === 0) { GAME.vacFilter = grainAt(ax, ay); if (GAME.vacFilter) hudDirty = true; }
|
if (GAME.vacFilter === 0) { GAME.vacFilter = grainAt(ax, ay); if (GAME.vacFilter) hudDirty = true; }
|
||||||
const f = GAME.vacFilter;
|
const f = GAME.vacFilter;
|
||||||
if (f !== 0) {
|
if (f !== 0) {
|
||||||
const r = toolVacuum(ax, ay, GAME.brush[1], f < 0 ? () => true : m => m === f);
|
const r = toolVacuum(ax, ay, GAME.brush[1], f === -2 ? m => GAME.blow.has(m) : f < 0 ? () => true : m => m === f);
|
||||||
if (r.got) { if (!GAME.blow.size) { GAME.blow = new Set([f > 0 ? f : r.m]); lastBagKey = ''; } playSfx('suck'); hudDirty = true; }
|
if (r.got) { playSfx('suck'); hudDirty = true; }
|
||||||
if (r.status) setStatus(r.status);
|
if (r.status) setStatus(r.status);
|
||||||
}
|
}
|
||||||
} else if (t === 'ignite') {
|
} else if (t === 'ignite') {
|
||||||
@@ -218,10 +218,10 @@ function toolTick() {
|
|||||||
}
|
}
|
||||||
if (GAME.rmb && !GAME.rmbConsumed) {
|
if (GAME.rmb && !GAME.rmbConsumed) {
|
||||||
if (t === 'vacuum') {
|
if (t === 'vacuum') {
|
||||||
if (!GAME.blowAll && !GAME.blow.size) { setStatus('Nothing in hand: Q/E or click something in the bag.'); return; }
|
if (!GAME.blowAll && !GAME.blow.size) { setStatus('Nothing selected: click or Q/E in the bag.'); return; }
|
||||||
const list = GAME.blowAll ? blowAllList() : blowList();
|
const list = GAME.blowAll ? blowAllList() : blowList();
|
||||||
|
if (!GAME.blowAll && !list.length) { setStatus('None of the selected materials are in the bag.'); return; }
|
||||||
const r = GAME.blowAll && !list.length ? { placed: 0, status: 'Nothing loose in the bag to blow.' } : toolBlow(ax, ay, GAME.brush[1], list[0] || 0, list.length > 1 ? list : null);
|
const r = GAME.blowAll && !list.length ? { placed: 0, status: 'Nothing loose in the bag to blow.' } : toolBlow(ax, ay, GAME.brush[1], list[0] || 0, list.length > 1 ? list : null);
|
||||||
for (const m of GAME.blow) if (bag[m] <= 0) { GAME.blow.delete(m); lastBagKey = ''; hudDirty = true; }
|
|
||||||
if (r.placed) { playSfx('blow'); hudDirty = true; } else if (r.status) setStatus(r.status);
|
if (r.placed) { playSfx('blow'); hudDirty = true; } else if (r.status) setStatus(r.status);
|
||||||
} else if (t === 'build') {
|
} else if (t === 'build') {
|
||||||
const key = `${(ax / BS) | 0},${(ay / BS) | 0}`;
|
const key = `${(ax / BS) | 0},${(ay / BS) | 0}`;
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ function pollPad() {
|
|||||||
if (padPressed(gp, 0)) doInteract();
|
if (padPressed(gp, 0)) doInteract();
|
||||||
if (padPressed(gp, 2)) rotate();
|
if (padPressed(gp, 2)) rotate();
|
||||||
if (padPressed(gp, 3)) toggleLens();
|
if (padPressed(gp, 3)) toggleLens();
|
||||||
if (padPressed(gp, 1)) { if (GAME.drag) GAME.drag = null; else if (GAME.paste) { GAME.paste = false; hudDirty = true; } }
|
if (padPressed(gp, 1)) { if (GAME.selDrag) { GAME.selDrag = null; hudDirty = true; } else if (GAME.drag) GAME.drag = null; else if (GAME.paste) { GAME.paste = false; hudDirty = true; } else if (toolId() === 'vacuum' && GAME.blow.size) { GAME.blow.clear(); lastBagKey = ''; hudDirty = true; playSfx('ui'); } }
|
||||||
if (padPressed(gp, 10)) bot.lamp = !bot.lamp;
|
if (padPressed(gp, 10)) bot.lamp = !bot.lamp;
|
||||||
if (padPressed(gp, 11)) { SET.zoom = SET.zoom >= 10 ? 2 : SET.zoom + 2; saveSettings(); resizeRender(); R.snapNext = true; }
|
if (padPressed(gp, 11)) { SET.zoom = SET.zoom >= 10 ? 2 : SET.zoom + 2; saveSettings(); resizeRender(); R.snapNext = true; }
|
||||||
if (padPressed(gp, 8)) openNotebook();
|
if (padPressed(gp, 8)) openNotebook();
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ function selectTool(k) {
|
|||||||
GAME.tool = k; GAME.drag = null; hudDirty = true; renderHotbar(); playSfx('ui');
|
GAME.tool = k; GAME.drag = null; hudDirty = true; renderHotbar(); playSfx('ui');
|
||||||
}
|
}
|
||||||
function bagMaterials() { return BAG_ORDER.filter(m => bag[m] > 0); }
|
function bagMaterials() { return BAG_ORDER.filter(m => bag[m] > 0); }
|
||||||
function blowList() { return [...GAME.blow]; }
|
function blowList() { return [...GAME.blow].filter(m => bag[m] > 0); }
|
||||||
// Shift + right click: an even mix of everything loose in the bag (bars never mix)
|
// Shift + right click: an even mix of everything loose in the bag (bars never mix)
|
||||||
function blowAllList() { return bagMaterials().filter(m => PH[m] !== PH_ITEM && (PH[m] !== PH_LIQUID || player.flask)); }
|
function blowAllList() { return bagMaterials().filter(m => PH[m] !== PH_ITEM && (PH[m] !== PH_LIQUID || player.flask)); }
|
||||||
function blowingAll() { return toolId() === 'vacuum' && (GAME.rmb ? !GAME.rmbConsumed && GAME.blowAll : modShift()); }
|
function blowingAll() { return toolId() === 'vacuum' && (GAME.rmb ? !GAME.rmbConsumed && GAME.blowAll : modShift()); }
|
||||||
|
|||||||
+3
-2
@@ -3,6 +3,7 @@
|
|||||||
const canvas = $('game');
|
const canvas = $('game');
|
||||||
function isTyping(e) { const t = e.target; return t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT'); }
|
function isTyping(e) { const t = e.target; return t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT'); }
|
||||||
window.addEventListener('keydown', e => {
|
window.addEventListener('keydown', e => {
|
||||||
|
if ((e.key === 'Control' || e.key === 'Meta') && !GAME.ctrl) { GAME.ctrl = true; lastBagKey = ''; hudDirty = true; }
|
||||||
if (isTyping(e)) { if (e.key === 'Escape') e.target.blur(); return; }
|
if (isTyping(e)) { if (e.key === 'Escape') e.target.blur(); return; }
|
||||||
const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
|
const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
|
||||||
if (anyModal()) {
|
if (anyModal()) {
|
||||||
@@ -43,8 +44,8 @@ window.addEventListener('keydown', e => {
|
|||||||
case 'Tab': $('bagpanel').hidden = !$('bagpanel').hidden; break;
|
case 'Tab': $('bagpanel').hidden = !$('bagpanel').hidden; break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener('keyup', e => { const k = e.key.length === 1 ? e.key.toLowerCase() : e.key; keys.delete(k); if (k === 'g') { GAME.replace = false; hudDirty = true; } if (k === 'c' || k === 'x') hudDirty = true; });
|
window.addEventListener('keyup', e => { const k = e.key.length === 1 ? e.key.toLowerCase() : e.key; keys.delete(k); if ((k === 'Control' || k === 'Meta') && GAME.ctrl) { GAME.ctrl = false; lastBagKey = ''; hudDirty = true; } if (k === 'g') { GAME.replace = false; hudDirty = true; } if (k === 'c' || k === 'x') hudDirty = true; });
|
||||||
window.addEventListener('blur', () => { keys.clear(); GAME.lmb = GAME.rmb = false; GAME.replace = false; GAME.selDrag = null; endRmbRecord(); });
|
window.addEventListener('blur', () => { keys.clear(); if (GAME.ctrl) { GAME.ctrl = false; lastBagKey = ''; hudDirty = true; } GAME.lmb = GAME.rmb = false; GAME.replace = false; GAME.selDrag = null; endRmbRecord(); });
|
||||||
canvas.addEventListener('mousemove', e => { GAME.mouse.x = e.clientX; GAME.mouse.y = e.clientY; GAME.mouse.in = true; if (GAME.usingPad) { GAME.usingPad = false; canvas.classList.remove('nocursor'); } });
|
canvas.addEventListener('mousemove', e => { GAME.mouse.x = e.clientX; GAME.mouse.y = e.clientY; GAME.mouse.in = true; if (GAME.usingPad) { GAME.usingPad = false; canvas.classList.remove('nocursor'); } });
|
||||||
canvas.addEventListener('mouseleave', () => { GAME.mouse.in = false; });
|
canvas.addEventListener('mouseleave', () => { GAME.mouse.in = false; });
|
||||||
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
||||||
|
|||||||
+6
-6
@@ -169,19 +169,19 @@ function copyCode(code) {
|
|||||||
function controlsHtml() {
|
function controlsHtml() {
|
||||||
const kb = [
|
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'],
|
['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'], ['Cycle what is in hand · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'],
|
['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'],
|
||||||
['Pick up what you click (Vacuum)', 'Left drag · Shift: everything'], ['Put in hand (Vacuum)', 'Click a bag name · Q/E'], ['Add to the mix (Vacuum)', 'Ctrl+click a bag name'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
|
['Pick up selection or what you click (Vacuum)', 'Left drag · Shift: everything'], ['Select (Vacuum)', 'Click a bag name · Q/E'], ['Add to selection (Vacuum)', 'Ctrl+click a bag name'], ['Show everything found (Vacuum)', 'Hold Ctrl in the bag'], ['Clear selection (Vacuum)', '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 tube directions', 'I'], ['Bot lamp', 'L'],
|
['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 tube directions', 'I'], ['Bot lamp', 'L'],
|
||||||
['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'M'],
|
['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'M'],
|
||||||
];
|
];
|
||||||
const pad = [
|
const pad = [
|
||||||
['Fly', 'Left stick'], ['Boost', 'Left stick pushed all the way'], ['Aim', 'Right stick'], ['Use tool · suck', 'RT'], ['Second action · blow', 'LT'], ['Tools', 'LB / RB'],
|
['Fly', 'Left stick'], ['Boost', 'Left stick pushed all the way'], ['Aim', 'Right stick'], ['Use tool · suck', 'RT'], ['Second action · blow', 'LT'], ['Tools', 'LB / RB'],
|
||||||
['Cycle what is in hand · piece', 'D-pad ← →'], ['Brush size', 'D-pad ↑ ↓'], ['Interact', 'A'], ['Variant · tune drill', 'X'], ['Thermal lens', 'Y'],
|
['Select one to blow · piece', 'D-pad ← →'], ['Brush size', 'D-pad ↑ ↓'], ['Interact', 'A'], ['Variant · tune drill', 'X'], ['Thermal lens', 'Y'],
|
||||||
['Cancel', 'B'], ['Zoom', 'R3'], ['Lamp', 'L3'], ['Notebook', 'View / Back'], ['Menu', 'Menu / Start'],
|
['Cancel · clear selection', 'B'], ['Zoom', 'R3'], ['Lamp', 'L3'], ['Notebook', 'View / Back'], ['Menu', 'Menu / Start'],
|
||||||
];
|
];
|
||||||
const tools = [
|
const tools = [
|
||||||
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
|
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
|
||||||
['2 Vacuum', "Left drag picks up what you click, locking onto the first loose material it reaches. Right click blows what is in hand. Click a bag name or use Q/E to choose one; Ctrl+click adds or removes it from the mix. Bars blow alone. Empty materials leave the hand. Picking up fills an empty hand. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
|
['2 Vacuum', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Selections stay when materials run out. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
|
||||||
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps and outlets, machines (fans, the Refiner) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'],
|
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps and outlets, machines (fans, the Refiner) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'],
|
||||||
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
|
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
|
||||||
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
|
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
|
||||||
@@ -191,7 +191,7 @@ function controlsHtml() {
|
|||||||
<p class="label grouphead">Keyboard and mouse</p>${tbl(kb)}<p class="label grouphead">Gamepad</p>${tbl(pad)}
|
<p class="label grouphead">Keyboard and mouse</p>${tbl(kb)}<p class="label grouphead">Gamepad</p>${tbl(pad)}
|
||||||
<p class="label grouphead">First steps</p>
|
<p class="label grouphead">First steps</p>
|
||||||
<p class="note">1. Drill down a little. Stone falls as rubble; coal (dark seams), copper ore (green speckles) and clay (reddish) come loose too.<br>
|
<p class="note">1. Drill down a little. Stone falls as rubble; coal (dark seams), copper ore (green speckles) and clay (reddish) come loose too.<br>
|
||||||
2. With the Vacuum, left drag to pick up the loose material you click. Shift takes everything.<br>
|
2. With the Vacuum, left drag to pick up the selection, or what you click if none. Shift takes everything.<br>
|
||||||
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.<br>
|
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.<br>
|
||||||
4. Ctrl+click coal and copper ore in the bag to blow a mix. Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.<br>
|
4. Ctrl+click coal and copper ore in the bag to blow a mix. Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.<br>
|
||||||
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.<br>
|
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.<br>
|
||||||
|
|||||||
+1
-1
@@ -153,7 +153,7 @@ function drawPreviews() {
|
|||||||
if (t === 'vacuum') {
|
if (t === 'vacuum') {
|
||||||
// what the right button would blow, next to the cursor; what the left is sucking, while it sucks
|
// what the right button would blow, next to the cursor; what the left is sucking, while it sucks
|
||||||
const sz = Math.max(6, zp * 1.6), qx = p.x + (r + 1.2) * zp, qy = p.y - (r + 1.2) * zp;
|
const sz = Math.max(6, zp * 1.6), qx = p.x + (r + 1.2) * zp, qy = p.y - (r + 1.2) * zp;
|
||||||
const swatches = GAME.lmb ? GAME.vacFilter > 0 ? [GAME.vacFilter] : [] : blowingAll() ? blowAllList() : blowList();
|
const swatches = GAME.lmb ? GAME.vacFilter === -2 ? [...GAME.blow] : GAME.vacFilter > 0 ? [GAME.vacFilter] : [] : blowingAll() ? blowAllList() : blowList();
|
||||||
swatches.slice(0, 4).forEach((m, k) => { ctx.fillStyle = rgbOf(m); ctx.fillRect(qx + k * (sz + 2), qy, sz, sz); });
|
swatches.slice(0, 4).forEach((m, k) => { ctx.fillStyle = rgbOf(m); ctx.fillRect(qx + k * (sz + 2), qy, sz, sz); });
|
||||||
if (blowing || GAME.lmb) {
|
if (blowing || GAME.lmb) {
|
||||||
// faint line from the nozzle: the path the grains take
|
// faint line from the nozzle: the path the grains take
|
||||||
|
|||||||
+24
-13
@@ -23,7 +23,7 @@ function cycleItem(d) {
|
|||||||
if (t === 'vacuum') {
|
if (t === 'vacuum') {
|
||||||
const l = bagMaterials();
|
const l = bagMaterials();
|
||||||
if (!l.length) return;
|
if (!l.length) return;
|
||||||
const k = GAME.blow.size ? l.indexOf(blowList()[0]) : d > 0 ? -1 : 0;
|
const first = blowList()[0], k = first ? l.indexOf(first) : d > 0 ? -1 : 0;
|
||||||
GAME.blow = new Set([l[(k + d + l.length) % l.length]]);
|
GAME.blow = new Set([l[(k + d + l.length) % l.length]]);
|
||||||
} else if (t === 'build') {
|
} else if (t === 'build') {
|
||||||
let k = BUILD_ORDER.indexOf(piece().id); k = (k + d + BUILD_ORDER.length) % BUILD_ORDER.length;
|
let k = BUILD_ORDER.indexOf(piece().id); k = (k + d + BUILD_ORDER.length) % BUILD_ORDER.length;
|
||||||
@@ -187,9 +187,9 @@ function renderSelCard() {
|
|||||||
const all = blowingAll(), list = all ? blowAllList() : blowList();
|
const all = blowingAll(), list = all ? blowAllList() : blowList();
|
||||||
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 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);
|
const count = list.reduce((n, m) => n + bag[m], 0);
|
||||||
h = `<div class="row vacrow"><b>Pick up</b><span>What you click · Shift: everything</span>${sws(GAME.lmb && GAME.vacFilter > 0 ? [GAME.vacFilter] : [])}</div>
|
h = `<div class="row vacrow"><b>Pick up</b><span>${GAME.blow.size ? 'Selected' : 'What you click'} · Shift: everything</span>${sws(GAME.blow.size ? [...GAME.blow] : GAME.lmb && GAME.vacFilter > 0 ? [GAME.vacFilter] : [])}</div>
|
||||||
<div class="row vacrow"><b>In hand</b><kbd>Q</kbd><span class="name grow vacchoice">${all ? '' : sws(list)}${all ? list.length ? `Everything · mix of ${list.length}` : 'Everything · nothing loose' : list.length > 1 ? `Mix of ${list.length}` : list.length ? esc(MD[list[0]].name) : 'empty'}</span><span class="num chip${count ? '' : ' bad'}">${count ? count.toLocaleString('en-US') : 'none'}</span><kbd>E</kbd></div>
|
<div class="row vacrow"><b>Blow out</b><kbd>Q</kbd><span class="name grow vacchoice">${all ? '' : sws(list)}${all ? list.length ? `Everything · mix of ${list.length}` : 'Everything · nothing loose' : list.length > 1 ? `Mix of ${list.length}` : list.length ? esc(MD[list[0]].name) : GAME.blow.size ? 'none in bag' : 'nothing selected'}</span><span class="num chip${count ? '' : ' bad'}">${count ? count.toLocaleString('en-US') : 'none'}</span><kbd>E</kbd></div>
|
||||||
<div class="sub">Left picks up what you click, right blows out what's in hand. Shift: everything. Click or Q/E in the bag to change the hand. <span class="chip">nozzle ${GAME.brush[1]}</span></div>`;
|
<div class="sub">Left picks up the selection (or what you click if nothing is selected), right blows it out. Hold Ctrl in the bag to pick from everything you've found. Shift: everything. <span class="chip">nozzle ${GAME.brush[1]}</span></div>`;
|
||||||
} else if (t === 'ignite') {
|
} 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>`;
|
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>`;
|
||||||
}
|
}
|
||||||
@@ -202,29 +202,40 @@ function renderBag() {
|
|||||||
$('bagcap').textContent = `${bagTotal.toLocaleString('en-US')} / ${cap.toLocaleString('en-US')}`;
|
$('bagcap').textContent = `${bagTotal.toLocaleString('en-US')} / ${cap.toLocaleString('en-US')}`;
|
||||||
$('bagbar').firstChild.style.width = pct + '%';
|
$('bagbar').firstChild.style.width = pct + '%';
|
||||||
$('bagbar').classList.toggle('full', bagTotal >= cap);
|
$('bagbar').classList.toggle('full', bagTotal >= cap);
|
||||||
const l = BAG_ORDER.filter(m => bag[m] > 0 || GAME.blow.has(m));
|
const t = toolId(), all = blowingAll(), blowing = all ? blowAllList() : [...GAME.blow];
|
||||||
const t = toolId(), all = blowingAll(), blowing = all ? blowAllList() : blowList();
|
const missing = [...GAME.blow].filter(m => bag[m] <= 0);
|
||||||
const key = l.map(m => m + ':' + bag[m] + (GAME.blow.has(m) ? 'b' : '')).join(',') + '|' + GAME.wallMat + '|' + GAME.filterCfg.m.join('.') + '|' + t + '|' + GAME.pieceIdx + '|' + all;
|
if (GAME.ctrl && t === 'vacuum') for (const m of BAG_ORDER) if (seen[m] && bag[m] <= 0 && !GAME.blow.has(m) && (PH[m] === PH_POWDER || PH[m] === PH_ITEM || PH[m] === PH_LIQUID && player.flask)) missing.push(m);
|
||||||
|
const l = bagMaterials().concat(missing);
|
||||||
|
const key = l.map(m => m + ':' + bag[m] + (GAME.blow.has(m) ? 'b' : '')).join(',') + '|' + GAME.wallMat + '|' + GAME.filterCfg.m.join('.') + '|' + t + '|' + GAME.pieceIdx + '|' + all + '|' + (t === 'vacuum' && GAME.ctrl) + '|' + missing.join('.');
|
||||||
if (key === lastBagKey) return;
|
if (key === lastBagKey) return;
|
||||||
lastBagKey = key;
|
lastBagKey = key;
|
||||||
const box = $('baglist'); box.innerHTML = '';
|
const box = $('baglist'); box.innerHTML = '';
|
||||||
if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Vacuum.')); return; }
|
if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Vacuum.')); return; }
|
||||||
for (const m of l) {
|
for (const m of l) {
|
||||||
|
if (m === missing[0]) box.appendChild(el('div', { class: 'bagsep' }, 'Not in bag'));
|
||||||
const sel = (t === 'vacuum' && blowing.includes(m)) || (t === 'build' && m === GAME.wallMat && pieceUsesWall(piece())) || (t === 'build' && piece().filter && GAME.filterCfg.m.includes(m));
|
const sel = (t === 'vacuum' && blowing.includes(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' : '') });
|
const row = el('div', { class: 'bagrow' + (bag[m] > 0 ? '' : ' none') + (sel ? ' on' : '') });
|
||||||
const b = el('button', { class: 'pick', title: WALL_OF[m] && t === 'build' ? `Build with ${MD[m].name}` : `Blow out ${MD[m].name}` });
|
const b = el('button', { class: 'pick', title: 'Select ' + 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.innerHTML = `<span class="sw" style="background:${rgbOf(m)}"></span><span class="nm">${esc(MD[m].name)}</span><span class="n num">${bag[m] > 0 ? bag[m].toLocaleString('en-US') : '–'}</span>`;
|
||||||
// Ctrl+click adds to or removes from the hand.
|
// Ctrl+click adds to or removes from the selection.
|
||||||
b.oncontextmenu = e => e.preventDefault();
|
b.oncontextmenu = e => e.preventDefault();
|
||||||
b.onclick = e => {
|
b.onclick = e => {
|
||||||
if (e.ctrlKey || e.metaKey) { toggleBlow(m); if (t !== 'vacuum') selectTool(1); return; }
|
if (e.ctrlKey || e.metaKey) { toggleBlow(m); if (t !== 'vacuum') selectTool(1); return; }
|
||||||
if (t === 'build' && piece().filter && filterable(m)) { toggleFilterMat(m); return; }
|
if (t === 'build' && piece().filter && filterable(m)) { toggleFilterMat(m); return; }
|
||||||
else if (t === 'build' && WALL_OF[m]) { GAME.wallMat = m; if (!pieceUsesWall(piece())) GAME.pieceIdx = 0; }
|
else if (t === 'build' && WALL_OF[m]) { GAME.wallMat = m; if (!pieceUsesWall(piece())) GAME.pieceIdx = 0; }
|
||||||
else { GAME.blow = new Set([m]); if (t !== 'vacuum') selectTool(1); }
|
else { GAME.blow = GAME.blow.size === 1 && GAME.blow.has(m) ? new Set() : new Set([m]); if (t !== 'vacuum') selectTool(1); }
|
||||||
hudDirty = true; lastBagKey = ''; playSfx('ui');
|
hudDirty = true; lastBagKey = ''; playSfx('ui');
|
||||||
};
|
};
|
||||||
row.appendChild(b);
|
row.appendChild(b);
|
||||||
box.appendChild(row);
|
box.appendChild(row);
|
||||||
}
|
}
|
||||||
if (t === 'vacuum') box.appendChild(el('div', { class: 'baghint' }, 'Click: put in hand · Ctrl+click: add to the mix · Q/E: next'));
|
if (t === 'vacuum') {
|
||||||
|
const hint = el('div', { class: 'baghint' }, "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next");
|
||||||
|
if (GAME.blow.size) {
|
||||||
|
const clear = el('button', { class: 'bagclear', 'data-bag-clear': '' }, 'Clear');
|
||||||
|
clear.onclick = () => { GAME.blow.clear(); lastBagKey = ''; hudDirty = true; playSfx('ui'); };
|
||||||
|
hint.appendChild(clear);
|
||||||
|
}
|
||||||
|
box.appendChild(hint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ function startNewWorld(seed, w, h, name = '') {
|
|||||||
GAME.playTime = 0; GAME.clip = null; GAME.paste = false; GAME.selDrag = null; GAME.tool = 0; GAME.blow.clear(); GAME.wallMat = RSTONE; GAME.filterCfg = { m: [], inv: 0 }; GAME.pieceIdx = 0;
|
GAME.playTime = 0; GAME.clip = null; GAME.paste = false; GAME.selDrag = null; GAME.tool = 0; GAME.blow.clear(); GAME.wallMat = RSTONE; GAME.filterCfg = { m: [], inv: 0 }; GAME.pieceIdx = 0;
|
||||||
showLoading(false);
|
showLoading(false);
|
||||||
enterGame();
|
enterGame();
|
||||||
showCard({ kind: 'Welcome', title: 'The dig site', body: ['Fly with WASD. The Drill (1) breaks rock loose. The Vacuum (2) picks up what you click. Right click blows what is in hand. Click or Q/E in the bag chooses the hand; Ctrl+click mixes, Shift takes or blows everything.', 'You owe $20,000 on this claim. Sell what you dig and make at the trade dock next to you: anything that drops through its grate is paid for on the spot, and buyers post orders that pay far more. Every tip lives in the notebook (N).'], hold: 1.8 });
|
showCard({ kind: 'Welcome', title: 'The dig site', body: ['Fly with WASD. The Drill (1) breaks rock loose. The Vacuum (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', 'You owe $20,000 on this claim. Sell what you dig and make at the trade dock next to you: anything that drops through its grate is paid for on the spot, and buyers post orders that pay far more. Every tip lives in the notebook (N).'], hold: 1.8 });
|
||||||
await saveWorld('auto');
|
await saveWorld('auto');
|
||||||
} catch (e) { showLoading(false); toast('Could not create a world. Browser storage is unavailable.'); }
|
} catch (e) { showLoading(false); toast('Could not create a world. Browser storage is unavailable.'); }
|
||||||
}, 30);
|
}, 30);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const ICONS = {
|
|||||||
const BAG_ORDER = [BARAU, BARAG, BARBZ, BARBS, BARST, BARFE, BARCU, BARSN, BARZN, BARGL, BARSB, AUORE, AGORE, ZNORE, SNORE, RSTONE, LCLAY, RBRICK, LDIRT, LWOOD, ACORN, COAL, CHARCOAL, CUORE, FEORE, SAND, GRAVEL, RGRAN, RBAS, CULLET,
|
const BAG_ORDER = [BARAU, BARAG, BARBZ, BARBS, BARST, BARFE, BARCU, BARSN, BARZN, BARGL, BARSB, AUORE, AGORE, ZNORE, SNORE, RSTONE, LCLAY, RBRICK, LDIRT, LWOOD, ACORN, COAL, CHARCOAL, CUORE, FEORE, SAND, GRAVEL, RGRAN, RBAS, CULLET,
|
||||||
SCRCU, SCRFE, SCRST, SCRSN, SCRZN, SCRAG, SCRAU, SCRBZ, SCRBS, ASH, SLAG, SNOW, WATER, MAGMA, MCU, MFE, MST, MGL, MSN, MZN, MAG, MAU, MBZ, MBS];
|
SCRCU, SCRFE, SCRST, SCRSN, SCRZN, SCRAG, SCRAU, SCRBZ, SCRBS, ASH, SLAG, SNOW, WATER, MAGMA, MCU, MFE, MST, MGL, MSN, MZN, MAG, MAU, MBZ, MBS];
|
||||||
const GAME = {
|
const GAME = {
|
||||||
|
ctrl: false,
|
||||||
running: false, paused: false, tool: 0, blow: new Set(), wallMat: RSTONE, filterCfg: { m: [], inv: 0 }, pieceIdx: 0, pieceDir: {},
|
running: false, paused: false, tool: 0, blow: new Set(), wallMat: RSTONE, filterCfg: { m: [], inv: 0 }, pieceIdx: 0, pieceDir: {},
|
||||||
brush: [3, 2, 0, 0, 0], tipsOpen: new Set(), cards: [], vacFilter: 0, rmbConsumed: false, rmbSeen: new Set(), lookHold: { x: 0, y: 0 },
|
brush: [3, 2, 0, 0, 0], tipsOpen: new Set(), cards: [], vacFilter: 0, rmbConsumed: false, rmbSeen: new Set(), lookHold: { x: 0, y: 0 },
|
||||||
paste: false, selDrag: null, rmbRec: null, dragDir: -1, replaceShown: false, clip: null, drag: null, aim: { x: 0, y: 0 }, mouse: { x: 0, y: 0, in: false },
|
paste: false, selDrag: null, rmbRec: null, dragDir: -1, replaceShown: false, clip: null, drag: null, aim: { x: 0, y: 0 }, mouse: { x: 0, y: 0, in: false },
|
||||||
|
|||||||
+16
-11
@@ -4,19 +4,20 @@ status: Review
|
|||||||
order: 2
|
order: 2
|
||||||
priority: P3
|
priority: P3
|
||||||
created: 2026-10-24
|
created: 2026-10-24
|
||||||
updated: 2026-10-24T17:34:39Z
|
updated: 2026-10-24T17:57:43Z
|
||||||
title: vacuum/blow tool rethink
|
title: vacuum/blow tool rethink
|
||||||
report: |
|
report: |
|
||||||
(agent) The Vacuum now picks up what you click and blows out what's in hand. Left drag takes
|
(agent) The Vacuum has one selection for picking up and blowing out. With anything selected, a
|
||||||
only the material it starts on (or the first loose grain it reaches); Shift takes everything.
|
left drag takes only the selected materials, wherever it starts; with nothing selected it takes
|
||||||
The hand is what right click blows: click a bag name or Q/E for one material, Ctrl+click to
|
what you click (or the first loose grain it reaches). Right click blows the selected materials
|
||||||
add to a mix, Shift + right click for everything loose. When something in hand runs out it
|
that are in the bag, as a mix. Click a bag name or Q/E selects one, Ctrl+click adds or removes.
|
||||||
leaves the hand; with nothing in hand, right click blows nothing and says how to choose.
|
Hold Ctrl and the bag also lists everything you've found, under "Not in bag", so you can select
|
||||||
Picking something up with an empty hand puts it in hand. The pick-up marks (R,
|
materials you don't have yet. Selected materials stay selected when they run out. Clear the
|
||||||
Ctrl+right-click) and the F vacuum menu are gone.
|
selection with the Clear button in the bag, by clicking the only selected row, or gamepad B.
|
||||||
To check: drag starting on copper ore in a mixed pile (only ore comes up); empty the hand and
|
Shift takes or blows everything.
|
||||||
pick up coal (coal goes in hand); Ctrl+click coal and copper ore and blow a mix into a furnace
|
To check: hold Ctrl, Ctrl+click two materials you don't have, then sweep a mixed pile (only
|
||||||
until one runs out (the other keeps going); Shift + right click.
|
those come up); blow a coal + ore mix until the coal runs out (ore keeps going, coal stays
|
||||||
|
selected); Clear, then drag on something (only that comes up).
|
||||||
---
|
---
|
||||||
|
|
||||||
# Context
|
# Context
|
||||||
@@ -31,3 +32,7 @@ I dont like the current tool. I want to completely rethink how to pick up and dr
|
|||||||
- [x] When the last of something in hand is blown out, it leaves the hand; an empty hand says so instead of blowing
|
- [x] When the last of something in hand is blown out, it leaves the hand; an empty hand says so instead of blowing
|
||||||
- [x] Picking something up with an empty hand puts it in hand
|
- [x] Picking something up with an empty hand puts it in hand
|
||||||
- [x] Remove the pick-up marks (R, Ctrl+right-click, the "Only marked" chip) and the F vacuum menu
|
- [x] Remove the pick-up marks (R, Ctrl+right-click, the "Only marked" chip) and the F vacuum menu
|
||||||
|
- [x] One selection for both: a drag picks up only the selected materials (or what you click when nothing is selected), and right click blows out the selected ones in the bag
|
||||||
|
- [x] Holding Ctrl lists every material found so far in the bag, below a "Not in bag" divider, so several can be selected before any are picked up
|
||||||
|
- [x] Selected materials stay selected when they run out; blowing skips them
|
||||||
|
- [x] An easy way to clear the selection (a Clear button, clicking the only selected row, gamepad B)
|
||||||
|
|||||||
+50
-21
@@ -228,34 +228,38 @@ with sync_playwright() as p:
|
|||||||
others = ev(f'bagTotal - bag[{tgt[2]}]')
|
others = ev(f'bagTotal - bag[{tgt[2]}]')
|
||||||
check('vacuum (left) picks the clicked material', got > 0, f"{ev(f'MD[{tgt[2]}].name')} x{got}")
|
check('vacuum (left) picks the clicked material', got > 0, f"{ev(f'MD[{tgt[2]}].name')} x{got}")
|
||||||
# Drive one tick at a time so the pile cannot fall between the click and pickup.
|
# Drive one tick at a time so the pile cannot fall between the click and pickup.
|
||||||
ev("""window.vacCase = (mode, hand = []) => {
|
ev("""window.vacCase = (mode, selection = []) => {
|
||||||
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, cells = [], aim = GAME.aim, blow = GAME.blow;
|
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, cells = [], aim = GAME.aim, blow = GAME.blow;
|
||||||
const oldBag = bag.slice(), total = bagTotal, flyers0 = flyers.length;
|
const oldBag = bag.slice(), total = bagTotal, flyers0 = flyers.length;
|
||||||
for (let dy = -6; dy <= 6; dy++) for (let dx = -6; dx <= 6; dx++) { const i = (y+dy)*W+x+dx; cells.push([i, mat[i], temp[i], flags[i]]); setCell(i, EMPTY, 20, 0); }
|
for (let dy = -6; dy <= 6; dy++) for (let dx = -6; dx <= 6; dx++) { const i = (y+dy)*W+x+dx; cells.push([i, mat[i], temp[i], flags[i]]); setCell(i, EMPTY, 20, 0); }
|
||||||
bag.fill(0); bagTotal = 0; GAME.blow = new Set(hand); GAME.aim = { x, y }; GAME.lmb = true;
|
bag.fill(0); bagTotal = 0; GAME.blow = new Set(selection); GAME.aim = { x, y }; GAME.lmb = true;
|
||||||
if (mode !== 'air') setCell(y*W+x+1, LDIRT, 20, 0);
|
if (mode !== 'air') setCell(y*W+x+1, LDIRT, 20, 0);
|
||||||
if (mode !== 'air') setCell(y*W+x, CUORE, 20, 0);
|
if (mode !== 'air') setCell(y*W+x, mode === 'dirt' ? LDIRT : CUORE, 20, 0);
|
||||||
|
if (selection.length) { setCell(y*W+x+2, COAL, 20, 0); setCell(y*W+x+3, CUORE, 20, 0); }
|
||||||
primaryPress(mode === 'shift');
|
primaryPress(mode === 'shift');
|
||||||
let waiting = true;
|
let waiting = true;
|
||||||
if (mode === 'air') { toolTick(); waiting = GAME.vacFilter === 0 && bagTotal === 0; setCell(y*W+x, CUORE, 20, 0); setCell(y*W+x+1, LDIRT, 20, 0); }
|
if (mode === 'air') { toolTick(); waiting = selection.length ? GAME.vacFilter === -2 : GAME.vacFilter === 0 && bagTotal === 0; setCell(y*W+x, CUORE, 20, 0); setCell(y*W+x+1, LDIRT, 20, 0); }
|
||||||
toolTick();
|
toolTick();
|
||||||
|
if (selection.length) { GAME.aim = { x: x + 2, y }; toolTick(); }
|
||||||
if (mode === 'ore') { GAME.aim = { x: x + 1, y }; toolTick(); }
|
if (mode === 'ore') { GAME.aim = { x: x + 1, y }; toolTick(); }
|
||||||
const result = { ore: bag[CUORE], dirt: bag[LDIRT], dirtStays: mat[y*W+x+1] === LDIRT, filter: GAME.vacFilter, hand: [...GAME.blow], waiting };
|
const result = { coal: bag[COAL], ore: bag[CUORE], dirt: bag[LDIRT], dirtStays: mat[y*W+x+1] === LDIRT, filter: GAME.vacFilter, selection: [...GAME.blow], waiting };
|
||||||
primaryRelease(false); GAME.lmb = false; GAME.aim = aim; GAME.blow = blow;
|
primaryRelease(false); GAME.lmb = false; GAME.aim = aim; GAME.blow = blow;
|
||||||
for (const [i, m, t, f] of cells) setCell(i, m, t, f);
|
for (const [i, m, t, f] of cells) setCell(i, m, t, f);
|
||||||
bag.set(oldBag); bagTotal = total; flyers.length = flyers0; return result;
|
bag.set(oldBag); bagTotal = total; flyers.length = flyers0; return result;
|
||||||
}""")
|
}""")
|
||||||
r = ev("vacCase('ore')")
|
r = ev("vacCase('ore')")
|
||||||
check('drag starting on ore takes only ore and leaves dirt', r['ore'] > 0 and r['dirt'] == 0 and r['dirtStays'] and r['filter'] == ev('CUORE'))
|
check('drag starting on ore takes only ore and leaves dirt', r['ore'] > 0 and r['dirt'] == 0 and r['dirtStays'] and r['filter'] == ev('CUORE'))
|
||||||
check('pickup puts ore in an empty hand', r['hand'] == [ev('CUORE')])
|
check('pickup leaves the selection empty', r['selection'] == [])
|
||||||
r = ev("vacCase('air')")
|
r = ev("vacCase('air')")
|
||||||
check('drag starting in air waits and locks onto the first loose grain', r['waiting'] and r['ore'] > 0 and r['dirt'] == 0 and r['filter'] == ev('CUORE'))
|
check('drag starting in air waits and locks onto the first loose grain', r['waiting'] and r['ore'] > 0 and r['dirt'] == 0 and r['filter'] == ev('CUORE'))
|
||||||
r = ev("vacCase('shift')")
|
r = ev("vacCase('shift', [COAL])")
|
||||||
check('Shift drag takes everything and puts the first pickup in hand', r['ore'] > 0 and r['dirt'] > 0 and r['filter'] == -1 and r['hand'] == [ev('CUORE')])
|
check('Shift drag takes everything and keeps the selection', r['ore'] > 0 and r['dirt'] > 0 and r['filter'] == -1 and r['selection'] == [ev('COAL')])
|
||||||
check('picking up ore keeps coal in hand', ev("vacCase('ore', [COAL]).hand.join() === String(COAL)"))
|
for mode in ['dirt', 'air']:
|
||||||
check('picking up ore keeps a chosen mix', ev("vacCase('ore', [COAL, RSTONE]).hand.join() === [COAL, RSTONE].join()"))
|
r = ev(f"vacCase('{mode}', [COAL, CUORE])")
|
||||||
check('selecting Vacuum leaves an empty hand empty', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); selectTool(0); selectTool(1); renderSelCard(); const ok = !GAME.blow.size && $('selcard').querySelector('.vacchoice').textContent === 'empty' && $('selcard').querySelector('.num.chip').textContent === 'none'; GAME.blow = old; return ok; })()"))
|
check(f'selected coal and ore are picked starting on {mode}, dirt stays', r['coal'] > 0 and r['ore'] > 0 and r['dirt'] == 0 and r['dirtStays'] and r['filter'] == -2 and r['selection'] == ev('[COAL, CUORE]'))
|
||||||
check('Q/E from an empty hand chooses the first or last bag material', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); cycleItem(1); const first = blowList()[0] === bagMaterials()[0]; GAME.blow.clear(); cycleItem(-1); const last = blowList()[0] === bagMaterials().at(-1); GAME.blow = old; return first && last; })()"))
|
check('selecting Vacuum leaves an empty selection empty', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); selectTool(0); selectTool(1); renderSelCard(); const ok = !GAME.blow.size && $('selcard').querySelector('.vacchoice').textContent === 'nothing selected' && $('selcard').querySelector('.num.chip').textContent === 'none'; GAME.blow = old; return ok; })()"))
|
||||||
|
check('Q/E when selection is absent from the bag chooses first or last', ev("(() => { const old = GAME.blow, n = bag[SAND]; bag[SAND] = 0; GAME.blow = new Set([SAND]); cycleItem(-1); const last = blowList()[0] === bagMaterials().at(-1); GAME.blow = new Set([SAND]); cycleItem(1); const first = blowList()[0] === bagMaterials()[0]; bag[SAND] = n; GAME.blow = old; return first && last; })()"))
|
||||||
|
check('Q/E from an empty selection chooses the first or last bag material', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); cycleItem(1); const first = blowList()[0] === bagMaterials()[0]; GAME.blow.clear(); cycleItem(-1); const last = blowList()[0] === bagMaterials().at(-1); GAME.blow = old; return first && last; })()"))
|
||||||
check('middle click chooses a loose material even with none in the bag', ev("""(() => {
|
check('middle click chooses a loose material even with none in the bag', ev("""(() => {
|
||||||
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, i = y*W+x;
|
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, i = y*W+x;
|
||||||
const old = [mat[i], temp[i], flags[i]], aim = GAME.aim, blow = GAME.blow, n = bag[LDIRT];
|
const old = [mat[i], temp[i], flags[i]], aim = GAME.aim, blow = GAME.blow, n = bag[LDIRT];
|
||||||
@@ -265,6 +269,7 @@ with sync_playwright() as p:
|
|||||||
})()"""))
|
})()"""))
|
||||||
pg.screenshot(path=str(out / '02-vacuum.png'))
|
pg.screenshot(path=str(out / '02-vacuum.png'))
|
||||||
|
|
||||||
|
ev('GAME.blow.clear(); hudDirty = true')
|
||||||
# 2b. the vacuum filter stays fixed while dragging through a mixed pile
|
# 2b. the vacuum filter stays fixed while dragging through a mixed pile
|
||||||
def mixed_pile(x0, y0):
|
def mixed_pile(x0, y0):
|
||||||
ev(f"{{ const r = mulberry32(3); for (let y = {y0}; y < {y0} + 6; y++) for (let x = {x0}; x < {x0} + 14; x++) setCell(y*W+x, r() < 0.5 ? CUORE : RSTONE, AMB[y], 0); }}")
|
ev(f"{{ const r = mulberry32(3); for (let y = {y0}; y < {y0} + 6; y++) for (let x = {x0}; x < {x0} + 14; x++) setCell(y*W+x, r() < 0.5 ? CUORE : RSTONE, AMB[y], 0); }}")
|
||||||
@@ -313,7 +318,7 @@ with sync_playwright() as p:
|
|||||||
pg.mouse.wheel(0, 100); settle(300)
|
pg.mouse.wheel(0, 100); settle(300)
|
||||||
refill('bag.fill(0)')
|
refill('bag.fill(0)')
|
||||||
|
|
||||||
# 3. right button blows what is in hand; empty materials leave it
|
# 3. right button blows the selection; exhausted materials stay selected
|
||||||
refill('bag[COAL] = 120; bag[RSTONE] = 600; bag[CUORE] = 60; bag[LCLAY] = 200; bag[LWOOD] = 120')
|
refill('bag[COAL] = 120; bag[RSTONE] = 600; bag[CUORE] = 60; bag[LCLAY] = 200; bag[LWOOD] = 120')
|
||||||
ok = select('2', 'GAME.blow.size === 1 && GAME.blow.has(COAL)')
|
ok = select('2', 'GAME.blow.size === 1 && GAME.blow.has(COAL)')
|
||||||
check('Q/E chooses coal to blow', ok, ev('[...GAME.blow]'))
|
check('Q/E chooses coal to blow', ok, ev('[...GAME.blow]'))
|
||||||
@@ -325,11 +330,13 @@ with sync_playwright() as p:
|
|||||||
settle(350); pg.mouse.up(button='right'); settle(800)
|
settle(350); pg.mouse.up(button='right'); settle(800)
|
||||||
check('right click blows coal out of the bag onto the ground', ev('bag[COAL]') < c0 and ev('flyers.length') == 0, f"{c0}->{ev('bag[COAL]')}")
|
check('right click blows coal out of the bag onto the ground', ev('bag[COAL]') < c0 and ev('flyers.length') == 0, f"{c0}->{ev('bag[COAL]')}")
|
||||||
refill('bag[COAL] = 3')
|
refill('bag[COAL] = 3')
|
||||||
|
ev('GAME.blow.clear(); lastBagKey = ""; hudDirty = true'); settle(80)
|
||||||
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
||||||
hold(dx_, dy_, 400, 'right'); settle(400)
|
hold(dx_, dy_, 400, 'right'); settle(400)
|
||||||
check('blowing the last material empties the hand', ev('bag[COAL] === 0 && !GAME.blow.size'), ev('[bag[COAL], ...GAME.blow]'))
|
check('blowing the last coal keeps it selected', ev('bag[COAL] === 0 && GAME.blow.has(COAL)'))
|
||||||
check('empty hand blows nothing and explains how to choose', ev("(() => { const n = bagTotal, f = flyers.length; GAME.rmb = true; secondaryPress(false); toolTick(); GAME.rmb = false; return bagTotal === n && flyers.length === f && GAME.status === 'Nothing in hand: Q/E or click something in the bag.'; })()"))
|
check('selected but none in bag blows nothing', ev("(() => { const n = bagTotal, f = flyers.length; GAME.rmb = true; secondaryPress(false); toolTick(); GAME.rmb = false; renderSelCard(); return bagTotal === n && flyers.length === f && GAME.status === 'None of the selected materials are in the bag.' && $('selcard').querySelector('.vacchoice').textContent === 'none in bag'; })()"))
|
||||||
check('a mix drops only the material that runs out', ev("(() => { const b = bag.slice(), total = bagTotal, f = flyers.length; bag.fill(0); bag[COAL] = 1; bag[CUORE] = 100; bagTotal = 101; GAME.blow = new Set([COAL, CUORE]); GAME.rmb = true; secondaryPress(false); toolTick(); GAME.rmb = false; const ok = !bag[COAL] && bag[CUORE] > 0 && GAME.blow.size === 1 && GAME.blow.has(CUORE); bag.set(b); bagTotal = total; flyers.length = f; return ok; })()"))
|
check('nothing selected blows nothing and explains how to choose', ev("(() => { GAME.blow.clear(); const n = bagTotal, f = flyers.length; GAME.rmb = true; secondaryPress(false); toolTick(); GAME.rmb = false; return bagTotal === n && flyers.length === f && GAME.status === 'Nothing selected: click or Q/E in the bag.'; })()"))
|
||||||
|
check('a mix keeps exhausted coal selected and continues blowing ore', ev("(() => { const b = bag.slice(), total = bagTotal, f = flyers.length; bag.fill(0); bag[COAL] = 1; bag[CUORE] = 100; bagTotal = 101; GAME.blow = new Set([COAL, CUORE]); GAME.rmb = true; secondaryPress(false); toolTick(); const ore = bag[CUORE]; toolTick(); GAME.rmb = false; const ok = !bag[COAL] && ore > 0 && bag[CUORE] < ore && GAME.blow.size === 2 && GAME.blow.has(COAL) && GAME.blow.has(CUORE); bag.set(b); bagTotal = total; flyers.length = f; return ok; })()"))
|
||||||
# Shift + right click blows an even mix of everything loose in the bag, without changing the choice
|
# Shift + right click blows an even mix of everything loose in the bag, without changing the choice
|
||||||
refill('bag[COAL] = 60; bag[CUORE] = 60')
|
refill('bag[COAL] = 60; bag[CUORE] = 60')
|
||||||
ev('GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;')
|
ev('GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;')
|
||||||
@@ -348,7 +355,7 @@ with sync_playwright() as p:
|
|||||||
mx_, my_ = cell_to_screen(bx - 26, gy - 6)
|
mx_, my_ = cell_to_screen(bx - 26, gy - 6)
|
||||||
pg.mouse.move(mx_, my_); pg.keyboard.down('Shift'); pg.mouse.down(button='right'); settle(400); pg.mouse.up(button='right'); pg.keyboard.up('Shift'); settle(600)
|
pg.mouse.move(mx_, my_); pg.keyboard.down('Shift'); pg.mouse.down(button='right'); settle(400); pg.mouse.up(button='right'); pg.keyboard.up('Shift'); settle(600)
|
||||||
check('Shift + right click blows everything loose in the bag', ev('bag[CUORE]') < 60 and ev('bag[COAL]') < 60 and ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], bag[CUORE]]'))
|
check('Shift + right click blows everything loose in the bag', ev('bag[CUORE]') < 60 and ev('bag[COAL]') < 60 and ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], bag[CUORE]]'))
|
||||||
refill('bag[COAL] = 60; bag[CUORE] = 60'); settle(200)
|
refill('bag[COAL] = 60; bag[CUORE] = 60'); ev('GAME.blow.clear(); lastBagKey = ""; hudDirty = true'); settle(200)
|
||||||
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
||||||
check('clicking a bag name chooses one blow material', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
check('clicking a bag name chooses one blow material', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||||
ore_row = pg.locator('#baglist .pick').filter(has_text='Copper ore')
|
ore_row = pg.locator('#baglist .pick').filter(has_text='Copper ore')
|
||||||
@@ -357,9 +364,9 @@ with sync_playwright() as p:
|
|||||||
ore_row.click(modifiers=['Control']); settle(80)
|
ore_row.click(modifiers=['Control']); settle(80)
|
||||||
check('Ctrl+click again takes it out of the mix', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
check('Ctrl+click again takes it out of the mix', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||||
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right'); settle(80)
|
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right'); settle(80)
|
||||||
check('right click on a bag row leaves the hand alone', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
check('right click on a bag row leaves the selection alone', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||||
check('bag rows have no pickup tag', '⬇' not in pg.inner_text('#baglist'))
|
check('bag rows have no pickup tag', '⬇' not in pg.inner_text('#baglist'))
|
||||||
check('bag hint describes the hand', pg.locator('#baglist .baghint').inner_text() == 'Click: put in hand · Ctrl+click: add to the mix · Q/E: next')
|
check('bag hint describes the selection', "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next" in pg.locator('#baglist .baghint').inner_text())
|
||||||
ore_row.click(modifiers=['Control']); settle(80)
|
ore_row.click(modifiers=['Control']); settle(80)
|
||||||
check('vacuum bag has no pick or blow buttons', pg.locator('#baglist [data-pick], #baglist [data-blow]').count() == 0)
|
check('vacuum bag has no pick or blow buttons', pg.locator('#baglist [data-pick], #baglist [data-blow]').count() == 0)
|
||||||
pg.mouse.move(mx_, my_); pg.mouse.down(button='right'); settle(500); pg.mouse.up(button='right'); settle(600)
|
pg.mouse.move(mx_, my_); pg.mouse.down(button='right'); settle(500); pg.mouse.up(button='right'); settle(600)
|
||||||
@@ -373,14 +380,33 @@ with sync_playwright() as p:
|
|||||||
pg.set_viewport_size({'width': width, 'height': height}); settle(200)
|
pg.set_viewport_size({'width': width, 'height': height}); settle(200)
|
||||||
pg.screenshot(path=str(out / f'vacuum-card{suffix}.png'))
|
pg.screenshot(path=str(out / f'vacuum-card{suffix}.png'))
|
||||||
check(f'vacuum panel fits at {width}', ev("(() => { const c = $('selcard'), b = $('bagpanel'); return [c, b].every(e => { const r = e.getBoundingClientRect(); return r.left >= 0 && r.right <= innerWidth && r.bottom <= innerHeight && e.scrollWidth <= e.clientWidth; }) && [...$('baglist').querySelectorAll('.bagrow')].every(e => e.scrollWidth <= e.clientWidth); })()"))
|
check(f'vacuum panel fits at {width}', ev("(() => { const c = $('selcard'), b = $('bagpanel'); return [c, b].every(e => { const r = e.getBoundingClientRect(); return r.left >= 0 && r.right <= innerWidth && r.bottom <= innerHeight && e.scrollWidth <= e.clientWidth; }) && [...$('baglist').querySelectorAll('.bagrow')].every(e => e.scrollWidth <= e.clientWidth); })()"))
|
||||||
|
ev("seen[SAND] = 1; bagTotal -= bag[SAND]; bag[SAND] = 0; GAME.blow = new Set([COAL]); lastBagKey = ''; renderBag(); window.bagPositions = [...$('baglist').querySelectorAll('.bagrow:not(.none)')].map(e => e.getBoundingClientRect().top); window.dispatchEvent(new KeyboardEvent('keydown', {key:'Control', ctrlKey:true})); renderBag()")
|
||||||
|
check('Ctrl reveals known materials below stable in-bag rows', ev("""GAME.ctrl && !!$('baglist').querySelector('.bagsep') && !!$('baglist').querySelector('.bagrow.none .pick[title="Select Sand"]') && bagPositions.every((y, i) => y === $('baglist').querySelectorAll('.bagrow:not(.none)')[i].getBoundingClientRect().top)"""))
|
||||||
|
pg.screenshot(path=str(out / 'vacuum-ctrl-900.png'))
|
||||||
|
pg.locator('#baglist .none .pick[title="Select Sand"]').click(modifiers=['Control']); settle(80)
|
||||||
|
check('Ctrl click selects a material not in the bag and keeps its row after release', ev("GAME.blow.has(SAND) && !GAME.ctrl && !!$('baglist').querySelector('.bagrow.none.on .pick[title=\"Select Sand\"]')"))
|
||||||
|
check('drag picks the newly selected missing material', ev("(() => { const aim = GAME.aim, x = Math.floor(bot.x)+6, y = Math.floor(bot.y)-6, i = y*W+x, old = [mat[i],temp[i],flags[i]]; setCell(i,SAND,20,0); GAME.aim = {x,y}; GAME.lmb = true; primaryPress(false); toolTick(); primaryRelease(false); GAME.lmb = false; GAME.aim = aim; const ok = bag[SAND] > 0; setCell(i,...old); return ok; })()"))
|
||||||
|
ev("GAME.blow.delete(SAND); bagTotal -= bag[SAND]; bag[SAND] = 0; window.dispatchEvent(new KeyboardEvent('keyup', {key:'Control'})); renderBag()")
|
||||||
|
check('Ctrl release hides unselected missing materials', ev("""!GAME.ctrl && !$('baglist').querySelector('.bagrow.none .pick[title="Select Sand"]')"""))
|
||||||
|
ev("window.dispatchEvent(new KeyboardEvent('keydown', {key:'Meta', metaKey:true})); renderBag()")
|
||||||
|
check('Meta reveals known materials too', ev("""GAME.ctrl && !!$('baglist').querySelector('.bagsep')"""))
|
||||||
|
ev("window.dispatchEvent(new KeyboardEvent('keyup', {key:'Meta'})); renderBag()")
|
||||||
|
check('Meta release hides unselected missing materials', ev('!GAME.ctrl'))
|
||||||
|
ev("window.dispatchEvent(new KeyboardEvent('keydown', {key:'Control', ctrlKey:true})); window.dispatchEvent(new Event('blur')); renderBag()")
|
||||||
|
check('blur clears the modifier', ev('!GAME.ctrl'))
|
||||||
|
pg.locator('[data-bag-clear]').click(); settle(80)
|
||||||
|
check('Clear clears the selection', ev('!GAME.blow.size'))
|
||||||
|
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
||||||
|
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
||||||
|
check('plain click on the only selected row clears it', ev('!GAME.blow.size'))
|
||||||
pg.set_viewport_size({'width': 1280, 'height': 800})
|
pg.set_viewport_size({'width': 1280, 'height': 800})
|
||||||
pg.keyboard.press('3'); settle(80)
|
pg.keyboard.press('3'); settle(80)
|
||||||
check('build bag has no vacuum toggles', pg.locator('#baglist .mark').count() == 0)
|
check('build bag has no vacuum toggles', pg.locator('#baglist .mark').count() == 0)
|
||||||
pg.keyboard.press('2'); settle(80)
|
pg.keyboard.press('2'); settle(80)
|
||||||
ev("GAME.blow = new Set([COAL, CUORE]); toggleBlow(BARCU)")
|
ev("GAME.blow = new Set([COAL, CUORE]); toggleBlow(BARCU)")
|
||||||
check('putting a bar in hand replaces the mix', ev('GAME.blow.size === 1 && GAME.blow.has(BARCU)'))
|
check('selecting a bar replaces the mix', ev('GAME.blow.size === 1 && GAME.blow.has(BARCU)'))
|
||||||
ev("toggleBlow(COAL)")
|
ev("toggleBlow(COAL)")
|
||||||
check('putting a grain in hand replaces a bar', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
check('selecting a grain replaces a bar', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||||
refill('bag[COAL] = 120')
|
refill('bag[COAL] = 120')
|
||||||
|
|
||||||
# 4. igniter lights it
|
# 4. igniter lights it
|
||||||
@@ -516,6 +542,7 @@ with sync_playwright() as p:
|
|||||||
settle(1500)
|
settle(1500)
|
||||||
pg.screenshot(path=str(out / '06-bars.png'))
|
pg.screenshot(path=str(out / '06-bars.png'))
|
||||||
pg.keyboard.press('2'); settle(60)
|
pg.keyboard.press('2'); settle(60)
|
||||||
|
ev('GAME.blow.clear(); lastBagKey = ""; hudDirty = true')
|
||||||
e0 = ev('[ents[0].ix + 2, ents[0].iy + 1]')
|
e0 = ev('[ents[0].ix + 2, ents[0].iy + 1]')
|
||||||
pg.mouse.move(*cell_to_screen(*e0)); settle(900)
|
pg.mouse.move(*cell_to_screen(*e0)); settle(900)
|
||||||
hold(*cell_to_screen(*e0), 900)
|
hold(*cell_to_screen(*e0), 900)
|
||||||
@@ -726,6 +753,8 @@ with sync_playwright() as p:
|
|||||||
check('gamepad RT digs where the right stick aims', ev('stats.dug') > d0, f"{d0}->{ev('stats.dug')}")
|
check('gamepad RT digs where the right stick aims', ev('stats.dug') > d0, f"{d0}->{ev('stats.dug')}")
|
||||||
ev("__pad.buttons[5].pressed = true;"); settle(100); ev("__pad.buttons[5].pressed = false;"); settle(100)
|
ev("__pad.buttons[5].pressed = true;"); settle(100); ev("__pad.buttons[5].pressed = false;"); settle(100)
|
||||||
check('gamepad RB switches to the vacuum', ev('GAME.tool') == 1)
|
check('gamepad RB switches to the vacuum', ev('GAME.tool') == 1)
|
||||||
|
ev("GAME.blow = new Set([COAL]); __pad.buttons[1].pressed = true; pollPad(); __pad.buttons[1].pressed = false; pollPad()")
|
||||||
|
check('gamepad B clears the Vacuum selection', ev('!GAME.blow.size'))
|
||||||
ev("selectTool(2); GAME.pieceIdx = PIECE_BY_ID.belt.index; GAME.pieceDir.belt = 0; __pad.buttons[2].pressed = true; pollPad(); __pad.buttons[2].pressed = false; pollPad()")
|
ev("selectTool(2); GAME.pieceIdx = PIECE_BY_ID.belt.index; GAME.pieceDir.belt = 0; __pad.buttons[2].pressed = true; pollPad(); __pad.buttons[2].pressed = false; pollPad()")
|
||||||
check('gamepad X rotates with the Build tool', ev('GAME.pieceDir.belt === 1'))
|
check('gamepad X rotates with the Build tool', ev('GAME.pieceDir.belt === 1'))
|
||||||
ev('selectTool(1)')
|
ev('selectTool(1)')
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ run('for (let k=0;k<5;k++) { toolBlow(80, 70, 1, COAL); simTick(); }');
|
|||||||
check('blown coal flies out of the bot', run('flyers.length') > 0, run('flyers.length'));
|
check('blown coal flies out of the bot', run('flyers.length') > 0, run('flyers.length'));
|
||||||
ticks(run, 120);
|
ticks(run, 120);
|
||||||
check('blown coal leaves the bag and lands on the ground', run('bag[COAL]') < c0 && run('flyers.length') === 0 && count(run, run('COAL'), 60, 80, 100, 99) > 0, `${c0}->${run('bag[COAL]')} landed ${count(run, run('COAL'), 60, 80, 100, 99)}`);
|
check('blown coal leaves the bag and lands on the ground', run('bag[COAL]') < c0 && run('flyers.length') === 0 && count(run, run('COAL'), 60, 80, 100, 99) > 0, `${c0}->${run('bag[COAL]')} landed ${count(run, run('COAL'), 60, 80, 100, 99)}`);
|
||||||
check('empty blow mix explains that nothing is in hand', run('toolBlow(80, 70, 1, COAL, [CUORE, BARCU]).status') === 'Nothing in hand: Q/E or click something in the bag.');
|
check('no selection explains how to select', run('toolBlow(80, 70, 1, 0).status') === 'Nothing selected: click or Q/E in the bag.');
|
||||||
|
check('unavailable blow mix explains none are in the bag', run('toolBlow(80, 70, 1, COAL, [CUORE, BARCU]).status') === 'None of the selected materials are in the bag.');
|
||||||
// bot flies through built walls but not through natural rock
|
// bot flies through built walls but not through natural rock
|
||||||
run('bag[RSTONE] += 64; bagTotal += 64; placeBlocks(BP_WALL, RSTONE, blockRect(30, 14, 30, 17))');
|
run('bag[RSTONE] += 64; bagTotal += 64; placeBlocks(BP_WALL, RSTONE, blockRect(30, 14, 30, 17))');
|
||||||
check('bot can overlap a built wall', !run('botOverlapSolid(121, 58)'));
|
check('bot can overlap a built wall', !run('botOverlapSolid(121, 58)'));
|
||||||
|
|||||||
Reference in New Issue
Block a user