Vacuum: Shift blows everything, Ctrl+click in the bag, one-line menu rows (t16)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -230,6 +230,9 @@ above, including:
|
|||||||
the Vacuum (when nothing in reach can be used) or a click on the card opens a menu with ⬇ Pick up
|
the Vacuum (when nothing in reach can be used) or a click on the card opens a menu with ⬇ Pick up
|
||||||
and ⬆ Blow toggles per material; the bag rows lose their buttons. T no longer does anything for
|
and ⬆ Blow toggles per material; the bag rows lose their buttons. T no longer does anything for
|
||||||
the vacuum. The owner found t6's version had too many modes.
|
the vacuum. The owner found t6's version had too many modes.
|
||||||
|
Later the same day the owner added: Shift + right click blows an even mix of everything loose in
|
||||||
|
the bag; Ctrl+click a bag name adds it to the blow mix and Ctrl+right-click to pick-up; the menu
|
||||||
|
is one line per material because the first version was too large.
|
||||||
|
|
||||||
## Ideas not built yet
|
## Ideas not built yet
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -395,16 +395,16 @@ The notebook (N): milestones, market, wallet, blueprints, controls.
|
|||||||
- functions: openNotebook, renderNotebook, fmtClock, orderName, orderLeft, renderMarketTab, fmtPrice, itemsText, renderWallet, renderBlueprintTab, copyCode, controlsHtml
|
- functions: openNotebook, renderNotebook, fmtClock, orderName, orderLeft, renderMarketTab, fmtPrice, itemsText, renderWallet, renderBlueprintTab, copyCode, controlsHtml
|
||||||
- variables: nbTab, MARKET_GROUPS
|
- variables: nbTab, MARKET_GROUPS
|
||||||
|
|
||||||
### src/ui/hud.js (37 lines)
|
### src/ui/hud.js (39 lines)
|
||||||
The tool hotbar and what is selected.
|
The tool hotbar and what is selected.
|
||||||
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, ensureBlow, piece
|
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, ensureBlow, piece
|
||||||
|
|
||||||
### src/ui/build-menu.js (102 lines)
|
### src/ui/build-menu.js (102 lines)
|
||||||
The build menu along the bottom: categories, piece tiles and their icons.
|
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 (270 lines)
|
### src/ui/selection.js (276 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, toggleCollect, toggleBlow, pickFilterUnderCursor, toggleFilterMat, toggleCollectUnderCursor, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openVacuumMenu, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleCollect, toggleBlow, pickFilterUnderCursor, toggleFilterMat, toggleCollectUnderCursor, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openVacuumMenu, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
||||||
- variables: lastBagKey
|
- variables: lastBagKey
|
||||||
|
|||||||
@@ -4,6 +4,19 @@ 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: t16 follow-up: Shift blows all, Ctrl+click in the bag, compact menu (Claude Code)
|
||||||
|
|
||||||
|
- The owner's subtask "shift to blow all" and two chat requests. Codex was out of credits, so
|
||||||
|
Claude made the edits.
|
||||||
|
- `input.js`/`actions.js`: `secondaryPress(shift)` sets `GAME.blowAll`; `hud.js` `blowAllList()`
|
||||||
|
(loose bag materials, no bars); `previews.js` shows it. `selection.js`: Ctrl+click a bag name
|
||||||
|
toggles blow, Ctrl+right-click toggles pick-up; the vacuum menu is one `.vrow` per material
|
||||||
|
(CSS in `style.css`). Text in the card, bag hint and `notebook.js`.
|
||||||
|
- `test/play.py`: Shift blow-all (replaces the old "Shift still blows only coal" check, per the
|
||||||
|
owner's change), Ctrl+click and Ctrl+right-click, plain right click does nothing, one line per
|
||||||
|
menu row at 1280 and 900.
|
||||||
|
- Tests: `run_all.py --browser` all pass (215 `play.py` checks). Screenshots checked.
|
||||||
|
|
||||||
## 2026-10-24: t16 Vacuum tool rework, again (Claude Code, Codex)
|
## 2026-10-24: t16 Vacuum tool rework, again (Claude Code, Codex)
|
||||||
|
|
||||||
- The owner: too many modes; chose an F menu with two pick-up states.
|
- The owner: too many modes; chose an F menu with two pick-up states.
|
||||||
|
|||||||
+18
-12
@@ -315,14 +315,14 @@ input.text { background: var(--soot-0); border: 1px solid var(--soot-4); border-
|
|||||||
.toast { animation: none; }
|
.toast { animation: none; }
|
||||||
}
|
}
|
||||||
/* wall material picker */
|
/* wall material picker */
|
||||||
:is(.wpick, .vpick) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; max-height: min(60vh, 490px); overflow-y: auto; padding: 2px; }
|
.wpick { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; max-height: min(60vh, 490px); overflow-y: auto; padding: 2px; }
|
||||||
:is(.wpick, .vpick) .wp { display: flex; align-items: flex-start; gap: 8px; text-align: left; padding: 8px; border-radius: 6px; border: 1px solid var(--soot-4); background: var(--soot-2); color: var(--bone); font: inherit; font-size: 12px; cursor: pointer; min-width: 0; }
|
.wpick .wp { display: flex; align-items: flex-start; gap: 8px; text-align: left; padding: 8px; border-radius: 6px; border: 1px solid var(--soot-4); background: var(--soot-2); color: var(--bone); font: inherit; font-size: 12px; cursor: pointer; min-width: 0; }
|
||||||
:is(.wpick, .vpick) .wp > span:last-child { min-width: 0; overflow-wrap: anywhere; }
|
.wpick .wp > span:last-child { min-width: 0; overflow-wrap: anywhere; }
|
||||||
:is(.wpick, .vpick) .wp span span { display: block; color: var(--ash); margin-top: 3px; }
|
.wpick .wp span span { display: block; color: var(--ash); margin-top: 3px; }
|
||||||
:is(.wpick, .vpick) .wp.short { opacity: 0.55; }
|
.wpick .wp.short { opacity: 0.55; }
|
||||||
:is(.wpick, .vpick) .wp:hover { background: #2d2724; }
|
.wpick .wp:hover { background: #2d2724; }
|
||||||
:is(.wpick, .vpick) .wp.on { border-color: #e59a52; background: #33261c; }
|
.wpick .wp.on { border-color: #e59a52; background: #33261c; }
|
||||||
:is(.wpick, .vpick) .wp:focus-visible, [data-wall-picker]:focus-visible { outline: 2px solid #e59a52; outline-offset: 1px; }
|
.wpick .wp:focus-visible, [data-wall-picker]:focus-visible { outline: 2px solid #e59a52; outline-offset: 1px; }
|
||||||
[data-wall-picker] { cursor: pointer; }
|
[data-wall-picker] { cursor: pointer; }
|
||||||
/* filter picker */
|
/* filter picker */
|
||||||
.fpick { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; max-height: min(52vh, 420px); overflow-y: auto; padding: 2px; }
|
.fpick { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; max-height: min(52vh, 420px); overflow-y: auto; padding: 2px; }
|
||||||
@@ -362,8 +362,14 @@ input.text { background: var(--soot-0); border: 1px solid var(--soot-4); border-
|
|||||||
#m-dialog { z-index: 21; }
|
#m-dialog { z-index: 21; }
|
||||||
#continue-meta { max-width: 72%; overflow-wrap: anywhere; text-align: right; }
|
#continue-meta { max-width: 72%; overflow-wrap: anywhere; text-align: right; }
|
||||||
|
|
||||||
.vpick { max-height: min(48vh, 390px); }
|
.vpick { display: grid; gap: 1px; max-height: min(56vh, 460px); overflow-y: auto; padding: 2px; }
|
||||||
.vtoggles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
|
.vrow { display: grid; grid-template-columns: 12px minmax(0, 1fr) auto 52px 52px; align-items: center; gap: 8px; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
||||||
.vtoggles button { padding: 4px 7px; font-size: 11px; }
|
.vrow:not(.vhead):hover { background: var(--soot-2); }
|
||||||
.vtoggles button.on { border-color: #e59a52; background: #533820; color: var(--bone); }
|
.vrow .sw { width: 12px; height: 12px; border-radius: 2px; }
|
||||||
|
.vrow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.vrow .num { color: var(--ash); }
|
||||||
|
.vrow.vhead { color: var(--ash-dim); white-space: nowrap; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||||
|
.vrow.vhead span:nth-child(n+4) { text-align: center; }
|
||||||
|
.vrow button { padding: 1px 0; min-height: 0; font-size: 12px; justify-content: center; }
|
||||||
|
.vrow button.on { border-color: #e59a52; background: #533820; color: var(--bone); }
|
||||||
.vacchoice { display: flex; align-items: center; gap: 4px; text-align: left; min-width: 0; background: transparent; border: 0; padding: 0; cursor: pointer; }
|
.vacchoice { display: flex; align-items: center; gap: 4px; text-align: left; min-width: 0; background: transparent; border: 0; padding: 0; cursor: pointer; }
|
||||||
|
|||||||
+4
-4
@@ -188,9 +188,9 @@ function primaryRelease(shift) {
|
|||||||
}
|
}
|
||||||
if (t === 'vacuum') { GAME.vacFilter = 0; hudDirty = true; }
|
if (t === 'vacuum') { GAME.vacFilter = 0; hudDirty = true; }
|
||||||
}
|
}
|
||||||
function secondaryPress() {
|
function secondaryPress(shift) {
|
||||||
const t = toolId();
|
const t = toolId();
|
||||||
GAME.rmbSeen = new Set();
|
GAME.rmbSeen = new Set(); GAME.blowAll = !!shift;
|
||||||
if (GAME.selDrag) { GAME.selDrag = null; hudDirty = true; GAME.rmbConsumed = true; }
|
if (GAME.selDrag) { GAME.selDrag = null; hudDirty = true; GAME.rmbConsumed = true; }
|
||||||
else if (GAME.paste) { GAME.paste = false; hudDirty = true; playSfx('ui'); GAME.rmbConsumed = true; }
|
else if (GAME.paste) { GAME.paste = false; hudDirty = true; playSfx('ui'); GAME.rmbConsumed = true; }
|
||||||
else if (t === 'build' && GAME.drag) { GAME.drag = null; GAME.dragDir = -1; GAME.rmbConsumed = true; }
|
else if (t === 'build' && GAME.drag) { GAME.drag = null; GAME.dragDir = -1; GAME.rmbConsumed = true; }
|
||||||
@@ -216,8 +216,8 @@ function toolTick() {
|
|||||||
if (GAME.rmb && !GAME.rmbConsumed) {
|
if (GAME.rmb && !GAME.rmbConsumed) {
|
||||||
if (t === 'vacuum') {
|
if (t === 'vacuum') {
|
||||||
ensureBlow();
|
ensureBlow();
|
||||||
const list = blowList();
|
const list = GAME.blowAll ? blowAllList() : blowList();
|
||||||
const r = 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);
|
||||||
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}`;
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ function selectTool(k) {
|
|||||||
}
|
}
|
||||||
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]; }
|
||||||
|
// 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)); }
|
||||||
// Empty materials stay chosen until the player changes the marks.
|
// Empty materials stay chosen until the player changes the marks.
|
||||||
function ensureBlow() {
|
function ensureBlow() {
|
||||||
if (GAME.blow.size) return;
|
if (GAME.blow.size) return;
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ canvas.addEventListener('mousedown', e => {
|
|||||||
auInit();
|
auInit();
|
||||||
GAME.usingPad = false;
|
GAME.usingPad = false;
|
||||||
if (e.button === 0) { GAME.lmb = true; primaryPress(e.shiftKey && !steering()); }
|
if (e.button === 0) { GAME.lmb = true; primaryPress(e.shiftKey && !steering()); }
|
||||||
else if (e.button === 2) { GAME.rmb = true; secondaryPress(); }
|
else if (e.button === 2) { GAME.rmb = true; secondaryPress(e.shiftKey && !steering()); }
|
||||||
else if (e.button === 1) { e.preventDefault(); pickUnderCursor(); }
|
else if (e.button === 1) { e.preventDefault(); pickUnderCursor(); }
|
||||||
});
|
});
|
||||||
window.addEventListener('mouseup', e => {
|
window.addEventListener('mouseup', e => {
|
||||||
|
|||||||
+2
-2
@@ -170,7 +170,7 @@ 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 you blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'],
|
['Tools', '1 – 4'], ['Cycle what you blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'],
|
||||||
['Mark/unmark what is under the cursor (Vacuum)', 'R'], ['Vacuum menu: what to pick up and blow', 'F (with the Vacuum, away from anything to use)'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
|
['Mark/unmark what is under the cursor (Vacuum)', 'R'], ['Vacuum menu: what to pick up and blow', 'F (with the Vacuum, away from anything to use)'], ['Add to blow · to pick up (Vacuum)', 'Ctrl+click · Ctrl+right-click a bag name'], ['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'],
|
||||||
];
|
];
|
||||||
@@ -181,7 +181,7 @@ function controlsHtml() {
|
|||||||
];
|
];
|
||||||
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 click picks up everything, or only marked materials once you mark some. F opens the vacuum menu to mark what to pick up (⬇) and blow (⬆). R over a grain marks it. Hold Shift while not flying to take everything. Right click blows the ⬆ materials as an even mix, bars one at a time. Click a bag name or use Q/E to blow only one; it stays chosen when empty.'],
|
['2 Vacuum', 'Left click picks up everything, or only marked materials once you mark some. F opens the vacuum menu to mark what to pick up (⬇) and blow (⬆). R over a grain marks it. Hold Shift while not flying to take everything. Right click blows the ⬆ materials as an even mix, bars one at a time; Shift + right click blows an even mix of everything loose in the bag. Click a bag name or use Q/E to blow only one; it stays chosen when empty. Ctrl+click a bag name adds it to what you blow; Ctrl+right-click adds it to what you pick up.'],
|
||||||
['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.'],
|
||||||
|
|||||||
+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.collect.size ? [...GAME.collect] : [] : blowList();
|
const swatches = GAME.lmb ? GAME.vacFilter >= 0 && GAME.collect.size ? [...GAME.collect] : [] : GAME.rmb && GAME.blowAll ? 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
|
||||||
|
|||||||
+10
-4
@@ -163,7 +163,10 @@ function openVacuumMenu() {
|
|||||||
const blow = blowList();
|
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>`
|
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>' : '')
|
+ (!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>');
|
+ (list.length ? `<div class="vpick"><div class="vrow vhead"><span></span><span></span><span></span><span>Pick up</span><span>Blow</span></div>${list.map(m => {
|
||||||
|
const n = esc(MD[m].name);
|
||||||
|
return `<div class="vrow"><span class="sw" style="background:${rgbOf(m)}"></span><span class="nm">${n}</span><span class="num">${bag[m] ? bag[m].toLocaleString('en-US') : 'none'}</span><button data-vm-pick="${m}" aria-pressed="${GAME.collect.has(m)}" aria-label="Pick up ${n}" title="Pick up ${n}" class="btn${GAME.collect.has(m) ? ' on' : ''}">⬇</button><button data-vm-blow="${m}" aria-pressed="${GAME.blow.has(m)}" aria-label="Blow out ${n}" title="Blow out ${n}" class="btn${GAME.blow.has(m) ? ' on' : ''}"${!bag[m] && !GAME.blow.has(m) ? ' disabled' : ''}>⬆</button></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-pick]').forEach(b => b.onclick = () => { toggleCollect(+b.dataset.vmPick); sync(); });
|
||||||
box.querySelectorAll('[data-vm-blow]').forEach(b => b.onclick = () => { toggleBlow(+b.dataset.vmBlow); sync(); });
|
box.querySelectorAll('[data-vm-blow]').forEach(b => b.onclick = () => { toggleBlow(+b.dataset.vmBlow); sync(); });
|
||||||
}
|
}
|
||||||
@@ -231,7 +234,7 @@ function renderSelCard() {
|
|||||||
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><button class="chip hot" data-vac-menu>${marks.length ? 'Only marked' : 'Everything'} · F</button>${sws(marks)}</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="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>`;
|
<div class="sub">Left picks up, right blows out. F chooses what. R marks what is under the cursor. Shift takes or blows 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>`;
|
||||||
}
|
}
|
||||||
@@ -257,7 +260,10 @@ function renderBag() {
|
|||||||
if (t === 'vacuum' && GAME.collect.has(m)) row.appendChild(el('span', { class: 'tag', title: 'Marked to pick up', 'aria-label': 'Marked to pick up' }, '⬇'));
|
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}` });
|
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.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 = () => {
|
// Ctrl+click adds to (or removes from) what you blow; Ctrl+right-click, what you pick up
|
||||||
|
b.oncontextmenu = e => { e.preventDefault(); if (!(e.ctrlKey || e.metaKey)) return; toggleCollect(m); if (t !== 'vacuum') selectTool(1); };
|
||||||
|
b.onclick = e => {
|
||||||
|
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 = new Set([m]); if (t !== 'vacuum') selectTool(1); }
|
||||||
@@ -266,5 +272,5 @@ function renderBag() {
|
|||||||
row.appendChild(b);
|
row.appendChild(b);
|
||||||
box.appendChild(row);
|
box.appendChild(row);
|
||||||
}
|
}
|
||||||
if (t === 'vacuum') box.appendChild(el('div', { class: 'baghint' }, 'F with the Vacuum: choose what to pick up and blow'));
|
if (t === 'vacuum') box.appendChild(el('div', { class: 'baghint' }, 'Click: blow only this · Ctrl+click: add to blow · Ctrl+right-click: add to pick up · F: vacuum menu'));
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-2
@@ -327,15 +327,28 @@ with sync_playwright() as p:
|
|||||||
refill('bag[COAL] = 3')
|
refill('bag[COAL] = 3')
|
||||||
hold(dx_, dy_, 400, 'right'); settle(400)
|
hold(dx_, dy_, 400, 'right'); settle(400)
|
||||||
check('chosen material stays chosen after it runs out', ev('bag[COAL] === 0 && GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], ...GAME.blow]'))
|
check('chosen material stays chosen after it runs out', ev('bag[COAL] === 0 && GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], ...GAME.blow]'))
|
||||||
# mark coal and ore: Shift no longer changes what is blown, the selection does
|
# 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.collect = new Set([COAL, CUORE]); GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;')
|
ev('GAME.collect = new Set([COAL, CUORE]); GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;')
|
||||||
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 still blows only the chosen coal', ev('bag[CUORE]') == 60 and ev('bag[COAL]') < 60, 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'); 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.click(modifiers=['Control']); settle(80)
|
||||||
|
check('Ctrl+click adds a bag item to the blow mix', ev('GAME.blow.size === 2 && GAME.blow.has(COAL) && GAME.blow.has(CUORE)') and 'Mix of 2' in pg.inner_text('#selcard'))
|
||||||
|
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)'))
|
||||||
|
ev('GAME.collect = new Set(); lastBagKey = ""; hudDirty = true'); settle(80)
|
||||||
|
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right', modifiers=['Control']); settle(80)
|
||||||
|
check('Ctrl+right-click marks a bag item for pick-up', ev('GAME.collect.size === 1 && GAME.collect.has(CUORE) && GAME.blow.size === 1'))
|
||||||
|
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right', modifiers=['Control']); settle(80)
|
||||||
|
check('Ctrl+right-click again unmarks it', ev('!GAME.collect.size'))
|
||||||
|
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right'); settle(80)
|
||||||
|
check('a plain right click on the bag changes nothing', ev('!GAME.collect.size && GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||||
|
ev('GAME.collect = new Set([COAL, CUORE]); lastBagKey = ""; hudDirty = true'); settle(80)
|
||||||
pick_before = ev('[...GAME.collect]')
|
pick_before = ev('[...GAME.collect]')
|
||||||
ev('GAME.blow = new Set([RSTONE]); hudDirty = true')
|
ev('GAME.blow = new Set([RSTONE]); hudDirty = true')
|
||||||
pg.click('#selcard [data-vac-menu]'); settle(80)
|
pg.click('#selcard [data-vac-menu]'); settle(80)
|
||||||
@@ -368,6 +381,7 @@ with sync_playwright() as p:
|
|||||||
pg.click('#selcard [data-vac-menu]'); settle(80)
|
pg.click('#selcard [data-vac-menu]'); settle(80)
|
||||||
pg.screenshot(path=str(out / f'vacuum-menu{suffix}.png'))
|
pg.screenshot(path=str(out / f'vacuum-menu{suffix}.png'))
|
||||||
check(f'vacuum menu fits at {width}', ev("(() => { const e = $('dlg-body'), r = e.closest('.plate').getBoundingClientRect(); return e.scrollWidth <= e.clientWidth && document.querySelector('.vpick').scrollWidth <= document.querySelector('.vpick').clientWidth && r.left >= 0 && r.right <= innerWidth && r.bottom <= innerHeight; })()"))
|
check(f'vacuum menu fits at {width}', ev("(() => { const e = $('dlg-body'), r = e.closest('.plate').getBoundingClientRect(); return e.scrollWidth <= e.clientWidth && document.querySelector('.vpick').scrollWidth <= document.querySelector('.vpick').clientWidth && r.left >= 0 && r.right <= innerWidth && r.bottom <= innerHeight; })()"))
|
||||||
|
check(f'vacuum menu has one line per material at {width}', ev("(() => { const rows = [...document.querySelectorAll('.vpick .vrow:not(.vhead)')]; return rows.length > 3 && rows.every(r => r.getBoundingClientRect().height <= 28); })()"))
|
||||||
pg.get_by_role('button', name='Blow one at a time', exact=True).click(); settle(80)
|
pg.get_by_role('button', name='Blow one at a time', exact=True).click(); settle(80)
|
||||||
check(f'Blow one at a time keeps menu open at {width}', ev("GAME.blow.size === 1 && !$('m-dialog').hidden"))
|
check(f'Blow one at a time keeps menu open at {width}', ev("GAME.blow.size === 1 && !$('m-dialog').hidden"))
|
||||||
pg.get_by_role('button', name='Done', exact=True).click(); settle(80)
|
pg.get_by_role('button', name='Done', exact=True).click(); settle(80)
|
||||||
|
|||||||
Reference in New Issue
Block a user