// The build menu along the bottom: categories, piece tiles and their icons. // ---------- Build menu ---------- const BUILD_CATS = [ ['Structure', ['wall', 'grate', 'gate']], ['Moving', ['belt', 'filter', 'tube']], ['Liquids', ['pipe', 'pump', 'outlet']], ['Machines', ['fan', 'grinder', 'saw', 'silo']], ['Molds', ['ingotmold', 'bitmold', 'tankmold']], ]; const BUILD_ORDER = BUILD_CATS.flatMap(c => c[1]); const PIECE_ICONS = { wall: '', grate: '', gate: '', belt: '', filter: '', tube: '', pipe: '', pump: '', outlet: '', fan: '', refiner: '', cutter: '', grinder: '', saw: '', silo: '', ingotmold: '', bitmold: '', tankmold: '', }; function pieceIcon(id) { return ``; } function catOf(id) { const c = BUILD_CATS.findIndex(([, ids]) => ids.includes(id)); return c < 0 ? 0 : c; } let lastBuildKey = '', lastBuildMenu = ''; function selectBuildCat(c) { GAME.buildCat = c; const ids = BUILD_CATS[c][1], keep = GAME.catPick && GAME.catPick[c]; GAME.pieceIdx = PIECE_BY_ID[ids.includes(keep) ? keep : ids[0]].index; GAME.catPick = GAME.catPick || {}; GAME.catPick[c] = piece().id; GAME.drag = null; hudDirty = true; lastBuildKey = ''; playSfx('ui'); } function cycleBuildCat(d) { const c = GAME.buildCat != null ? GAME.buildCat : catOf(piece().id); selectBuildCat((c + d + BUILD_CATS.length) % BUILD_CATS.length); } function buildVariantChips(P) { const dir = P.dirs ? P.dirs[GAME.pieceDir[P.id] || 0] : null; // while dragging a line of belts, filters or tubes, the direction is the drag's const dragging = GAME.drag && GAME.dragDir >= 0 && P.dirs; const glyph = d => ({ left: '←', right: '→', up: '↑', down: '↓' })[d] || ''; const dirChip = !P.dirs ? '' : dragging ? `${glyph(P.dirs[GAME.dragDir])} ${esc(P.dirs[GAME.dragDir])} · from your drag` : `${glyph(dir)} ${esc(dir)} · R`; const fc = GAME.filterCfg; const sws = ms => ms.slice(0, 6).map(m => ``).join('') + (ms.length > 6 ? `+${ms.length - 6}` : ''); return pieceUsesWall(P) ? `` : P.filter ? `${fc.inv ? 'carries' : 'drops'} ${fc.m.length ? sws(fc.m) : 'nothing'} · F${dirChip}` : dir ? dirChip : ''; } function renderBuildBar() { const bar = $('buildbar'); if (!bar) return; const show = GAME.running && toolId() === 'build' && !GAME.paste && !GAME.selDrag && !selModeHeld(); if (bar.hidden === show) { bar.hidden = !show; $('hud').classList.toggle('building', show); } if (!show) { lastBuildKey = ''; return; } const P = piece(), cat = GAME.buildCat != null ? GAME.buildCat : catOf(P.id), hov = GAME.bbHover; const ids = BUILD_CATS[cat][1], tip = PIECE_BY_ID[GAME.usingPad ? P.id : hov && ids.includes(hov) ? hov : '']; const afford = ids.map(id => canAfford(pieceCostNow(PIECE_BY_ID[id]), 1) ? 1 : 0).join(''); const key = [cat, P.id, tip && tip.id, afford, GAME.wallMat, Math.round(money), GAME.pieceDir[P.id], GAME.filterCfg.inv, GAME.filterCfg.m.join('.'), innerWidth, innerHeight, SET.uiScale].join('|'); if (key === lastBuildKey) return; lastBuildKey = key; let h = '
' + BUILD_CATS.map(([name], k) => ``).join('') + 'Q/E cycle · Shift+Q/E tab
'; for (const id of ids) { const Q = PIECE_BY_ID[id], cost = pieceCostNow(Q), ok = canAfford(cost, 1); const sw = Q.variants === 'wall' ? `` : ''; h += ``; } h += '
'; if (!bar.querySelector('.bb-menu')) bar.innerHTML = '
'; const menu = bar.querySelector('.bb-menu'); if (lastBuildMenu !== h) { menu.innerHTML = h; lastBuildMenu = h; } const pop = $('bb-pop'); pop.hidden = !tip; if (!tip) return; const size = tip.kind === 'machine' ? `${tip.bw}×${tip.bh} blocks` : tip.kind === 'mold' ? `${MOLDS[tip.mold].bw}×${MOLDS[tip.mold].bh} blocks` : '1 block'; pop.innerHTML = `
${pieceIcon(tip.id)}${esc(tip.name)}
${chipCost(pieceCostNow(tip))}${size}${tip.power ? `${tip.power} power` : ''}
${esc(tip.desc)}
How to place ${tip.kind === 'block' ? 'Drag for a line, Shift-drag for a rectangle; out of reach becomes ghosts.' : 'Click to place.'}${tip.dirs && tip.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses R.' : tip.dirs ? ' R turns it.' : pieceUsesWall(tip) ? ' R cycles materials; F opens the material menu.' : ''}
${tip.filter ? `
New ones will ${esc(filterSentence(GAME.filterCfg))}. F sets materials and mode (or click materials in the bag, or T over one); F on a placed filter changes its whole row.
` : ''}
hold G replace · Ctrl+Z undo · right click take back · middle mouse pick · hold C/X copy/cut · V paste
`; const tile = bar.querySelector(`[data-piece="${tip.id}"]`); if (!tile) { pop.hidden = true; return; } const r = tile.getBoundingClientRect(), br = bar.getBoundingClientRect(), w = pop.getBoundingClientRect().width; pop.style.left = `${Math.max(8, Math.min(innerWidth - w - 8, r.left + r.width / 2 - w / 2)) - br.left}px`; } function setupBuildBar() { $('selcard').addEventListener('click', e => { if (e.target.closest('[data-wall-picker]')) openWallPicker(); }); const bar = $('buildbar'); if (!bar) return; bar.addEventListener('click', e => { const t = e.target.closest('[data-cat],[data-piece]'); if (!t) return; if (t.dataset.cat != null) { selectBuildCat(+t.dataset.cat); return; } GAME.pieceIdx = PIECE_BY_ID[t.dataset.piece].index; GAME.buildCat = catOf(t.dataset.piece); GAME.catPick = GAME.catPick || {}; GAME.catPick[catOf(piece().id)] = piece().id; GAME.drag = null; hudDirty = true; lastBuildKey = ''; playSfx('ui'); }); bar.addEventListener('mouseover', e => { const t = e.target.closest('[data-piece]'); const id = t ? t.dataset.piece : null; if (id !== GAME.bbHover) { GAME.bbHover = id; } }); bar.addEventListener('mouseleave', () => { GAME.bbHover = null; }); // clicks on the menu never reach the world bar.addEventListener('mousedown', e => e.stopPropagation()); }