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>
61 lines
4.4 KiB
JavaScript
61 lines
4.4 KiB
JavaScript
// Gamepad input.
|
|
// ---------- Gamepad ----------
|
|
const PAD = { prev: [], active: false, menuT: 0 };
|
|
function padPressed(gp, b) { const v = gp.buttons[b] && gp.buttons[b].pressed; const was = PAD.prev[b]; return v && !was; }
|
|
function pollPad() {
|
|
const gps = navigator.getGamepads ? navigator.getGamepads() : [];
|
|
let gp = null; for (const g of gps) if (g && g.connected) { gp = g; break; }
|
|
if (!gp) return { mx: 0, my: 0 };
|
|
const dz = SET.deadzone;
|
|
const ax = v => Math.abs(v) < dz ? 0 : (v - Math.sign(v) * dz) / (1 - dz);
|
|
const lx = ax(gp.axes[0] || 0), ly = ax(gp.axes[1] || 0), rx = ax(gp.axes[2] || 0), ry = ax(gp.axes[3] || 0);
|
|
const pressedAny = gp.buttons.some(b => b && b.pressed) || lx || ly || rx || ry;
|
|
if (pressedAny && !GAME.usingPad) { GAME.usingPad = true; canvas.classList.add('nocursor'); }
|
|
const modal = topModal();
|
|
if (modal || !GAME.running) {
|
|
const now = performance.now();
|
|
const up = padPressed(gp, 12) || (ly < -0.6 && now > PAD.menuT), down = padPressed(gp, 13) || (ly > 0.6 && now > PAD.menuT);
|
|
const left = padPressed(gp, 14) || (lx < -0.6 && now > PAD.menuT), right = padPressed(gp, 15) || (lx > 0.6 && now > PAD.menuT);
|
|
if (up || down || left || right) PAD.menuT = now + 220;
|
|
const root = modal ? $(modal) : $('title');
|
|
const focusables = [...root.querySelectorAll('button:not([disabled]), select, input')].filter(e => e.offsetParent !== null);
|
|
const cur = document.activeElement;
|
|
let idx = focusables.indexOf(cur);
|
|
if (up || down) { idx = idx < 0 ? 0 : (idx + (down ? 1 : -1) + focusables.length) % focusables.length; focusables[idx] && focusables[idx].focus(); }
|
|
if ((left || right) && cur) {
|
|
if (cur.type === 'range') { const st = parseFloat(cur.step) || 1; cur.value = Math.max(+cur.min, Math.min(+cur.max, parseFloat(cur.value) + (right ? st : -st))); cur.dispatchEvent(new Event('input')); }
|
|
else if (cur.tagName === 'SELECT') { cur.selectedIndex = Math.max(0, Math.min(cur.options.length - 1, cur.selectedIndex + (right ? 1 : -1))); cur.dispatchEvent(new Event('change')); }
|
|
else if (cur.closest('.tabs')) { const tb = [...cur.parentNode.children]; const k = tb.indexOf(cur); const n = tb[(k + (right ? 1 : -1) + tb.length) % tb.length]; n.click(); setTimeout(() => { const t2 = root.querySelector('.tabs .on'); t2 && t2.focus(); }, 0); }
|
|
}
|
|
if (padPressed(gp, 0) && cur && focusables.includes(cur)) cur.click();
|
|
if (padPressed(gp, 1) && modal) closeModal(modal);
|
|
if (padPressed(gp, 9) && modal === 'm-pause') closeModal(modal);
|
|
PAD.prev = gp.buttons.map(b => b && b.pressed);
|
|
return { mx: 0, my: 0 };
|
|
}
|
|
const mag = Math.hypot(rx, ry);
|
|
const reachNow = toolId() === 'drill' ? drillReach(GAME.drillW) : REACH;
|
|
if (mag > 0.05) { GAME.padAim.x = rx / Math.max(1, mag) * reachNow * SET.aimSpeed * Math.min(1, mag); GAME.padAim.y = ry / Math.max(1, mag) * reachNow * SET.aimSpeed * Math.min(1, mag); }
|
|
if (padPressed(gp, 4)) selectTool((GAME.tool + TOOLS.length - 1) % TOOLS.length);
|
|
if (padPressed(gp, 5)) selectTool((GAME.tool + 1) % TOOLS.length);
|
|
if (padPressed(gp, 14)) cycleItem(-1);
|
|
if (padPressed(gp, 15)) cycleItem(1);
|
|
if (padPressed(gp, 12)) setBrush(1);
|
|
if (padPressed(gp, 13)) setBrush(-1);
|
|
if (padPressed(gp, 0)) doInteract();
|
|
if (padPressed(gp, 2)) rotate();
|
|
if (padPressed(gp, 3)) toggleLens();
|
|
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, 11)) { SET.zoom = SET.zoom >= 10 ? 2 : SET.zoom + 2; saveSettings(); resizeRender(); R.snapNext = true; }
|
|
if (padPressed(gp, 8)) openNotebook();
|
|
if (padPressed(gp, 9)) openPause();
|
|
const rt = gp.buttons[7] && (gp.buttons[7].pressed || gp.buttons[7].value > 0.4), lt = gp.buttons[6] && (gp.buttons[6].pressed || gp.buttons[6].value > 0.4);
|
|
if (GAME.usingPad) {
|
|
if (rt && !GAME.lmb) { GAME.lmb = true; primaryPress(false); } else if (!rt && GAME.lmb) { primaryRelease(false); GAME.lmb = false; }
|
|
if (lt && !GAME.rmb) { GAME.rmb = true; secondaryPress(); } else if (!lt && GAME.rmb) { GAME.rmb = false; endRmbRecord(); }
|
|
}
|
|
PAD.prev = gp.buttons.map(b => b && b.pressed);
|
|
return { mx: lx, my: ly };
|
|
}
|