Identify ores with the cursor: pinged veins, ores-only label, vein outline (t77)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+20
-12
@@ -183,6 +183,7 @@ The frame loop applies speed and queued steps while keeping the per-frame tick l
|
|||||||
| Mouse building and dragging | `src/ui/actions.js` (`primaryPress`, `primaryRelease`, `secondaryPress`, `toolTick`) |
|
| Mouse building and dragging | `src/ui/actions.js` (`primaryPress`, `primaryRelease`, `secondaryPress`, `toolTick`) |
|
||||||
| Cursor previews (ghosts, drill aim, selection box) | `src/ui/previews.js` |
|
| Cursor previews (ghosts, drill aim, selection box) | `src/ui/previews.js` |
|
||||||
| Controls help text | `src/ui/notebook.js` (`controlsHtml`) |
|
| Controls help text | `src/ui/notebook.js` (`controlsHtml`) |
|
||||||
|
| Cached vein membership, ore identification and label timing | `src/ui/cursor.js` |
|
||||||
| Hover info and discovery cards | `src/ui/info.js` |
|
| Hover info and discovery cards | `src/ui/info.js` |
|
||||||
| Milestones | `src/sim/progress.js` (`MILESTONES`), checked in `src/sim/ledger.js` |
|
| Milestones | `src/sim/progress.js` (`MILESTONES`), checked in `src/sim/ledger.js` |
|
||||||
| Starter furnace designs | `src/sim/progress.js` (`STARTER_DESIGNS`, `smelterBlueprint`) |
|
| Starter furnace designs | `src/sim/progress.js` (`STARTER_DESIGNS`, `smelterBlueprint`) |
|
||||||
@@ -310,7 +311,7 @@ Molds: cavities for castings; ingot troughs empty themselves below. Removal retu
|
|||||||
- functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, tally, collectMold, removeMold
|
- functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, tally, collectMold, removeMold
|
||||||
- variables: DROSS, grains, barWord
|
- variables: DROSS, grains, barWord
|
||||||
|
|
||||||
### src/sim/items.js (317 lines)
|
### src/sim/items.js (318 lines)
|
||||||
Bars and other loose objects (ents), and the bag: adding, taking, costs and refunds.
|
Bars and other loose objects (ents), and the bag: adding, taking, costs and refunds.
|
||||||
- functions: entSolidCell, entSupportCount, entFreeAt, entMark, spawnBar, removeEnt, entScrewAt, entScrewFree, entScrewMove, entScrewLane, entScrewEdge, entScrewEnter, entScrewTick, entDiagonalScrewTick, entBeltScrewEnter, entSlopeBeltTick, entTick, entsInCircle, bagAdd, bagTake, giveOrDrop, refundAt, canAfford, payCost, fmtMoney, costText, needText
|
- functions: entSolidCell, entSupportCount, entFreeAt, entMark, spawnBar, removeEnt, entScrewAt, entScrewFree, entScrewMove, entScrewLane, entScrewEdge, entScrewEnter, entScrewTick, entDiagonalScrewTick, entBeltScrewEnter, entSlopeBeltTick, entTick, entsInCircle, bagAdd, bagTake, giveOrDrop, refundAt, canAfford, payCost, fmtMoney, costText, needText
|
||||||
- variables: BAR_W, BAR_H, entCurM, droppedLast
|
- variables: BAR_W, BAR_H, entCurM, droppedLast
|
||||||
@@ -375,12 +376,12 @@ World generation helpers: noise and removal of tiny hard rock specks.
|
|||||||
World generation: staged terrain, ore, caves and placement, with synchronous and async entry points.
|
World generation: staged terrain, ore, caves and placement, with synchronous and async entry points.
|
||||||
- functions: generateWorld, generateWorldAsync
|
- functions: generateWorld, generateWorldAsync
|
||||||
|
|
||||||
### src/sim/devconsole.js (113 lines)
|
### src/sim/devconsole.js (114 lines)
|
||||||
Dev commands are pure simulation operations; the browser owns the panel and confirmation.
|
Dev commands are pure simulation operations; the browser owns the panel and confirmation.
|
||||||
- functions: resetDevConsole, enableDevConsole, devMaterial, devCommand
|
- functions: resetDevConsole, enableDevConsole, devMaterial, devCommand
|
||||||
- variables: devWorld, devLog, devFree, devSpeed, devPaused, devSteps, DEV_HELP, DEV_MATERIALS
|
- variables: devWorld, devLog, devFree, devSpeed, devPaused, devSteps, DEV_HELP, DEV_MATERIALS
|
||||||
|
|
||||||
### src/sim/save.js (183 lines)
|
### src/sim/save.js (185 lines)
|
||||||
Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration.
|
Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration.
|
||||||
- functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick
|
- functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick
|
||||||
- variables: SAVE_VERSION, TOFF
|
- variables: SAVE_VERSION, TOFF
|
||||||
@@ -412,10 +413,10 @@ Scale colour without changing the smooth-look class.
|
|||||||
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
||||||
- functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks
|
- functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks
|
||||||
|
|
||||||
### src/render/overlays.js (321 lines)
|
### src/render/overlays.js (370 lines)
|
||||||
Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles.
|
Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles.
|
||||||
- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, pingImage, drawPing, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles
|
- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, pingImage, drawPing, drawHoveredVein, drawPingRecharge, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles
|
||||||
- variables: PING_SPEED, PING_FADE
|
- variables: PING_SPEED, PING_HOLD, PING_FADE, PING_BANDS, PING_GLINTS, GLINT_MS
|
||||||
|
|
||||||
### src/render/smooth.js (99 lines)
|
### src/render/smooth.js (99 lines)
|
||||||
Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
||||||
@@ -435,7 +436,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 (75 lines)
|
### src/ui/state.js (76 lines)
|
||||||
Game shell (src/ui/): browser only. This file: settings, the GAME state object, DOM helpers, toasts and tool status.
|
Game shell (src/ui/): browser only. This file: settings, the GAME state object, DOM helpers, toasts and tool status.
|
||||||
- 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, $
|
||||||
@@ -478,9 +479,10 @@ Notebook market: prices, orders, claim payments and wallet.
|
|||||||
Notebook blueprints: starter designs, saved library and code sharing.
|
Notebook blueprints: starter designs, saved library and code sharing.
|
||||||
- functions: renderBlueprintTab, copyCode
|
- functions: renderBlueprintTab, copyCode
|
||||||
|
|
||||||
### src/ui/hud.js (34 lines)
|
### src/ui/hud.js (40 lines)
|
||||||
The tool hotbar and what is selected.
|
The tool hotbar and what is selected.
|
||||||
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, piece
|
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, piece
|
||||||
|
- variables: IN_BAG_ORDER
|
||||||
|
|
||||||
### src/ui/build-menu.js (111 lines)
|
### src/ui/build-menu.js (111 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.
|
||||||
@@ -500,7 +502,12 @@ Selection panels: current tool card and bag materials.
|
|||||||
- functions: renderSelCard, renderBag
|
- functions: renderSelCard, renderBag
|
||||||
- variables: lastBagKey
|
- variables: lastBagKey
|
||||||
|
|
||||||
### src/ui/info.js (201 lines)
|
### src/ui/cursor.js (47 lines)
|
||||||
|
Hover work is shared by the cursor label and the vein outline.
|
||||||
|
- functions: oreLike, bitNeeded, pingVisible, cursorVein, hoveredVein, cursorLabelAlpha
|
||||||
|
- variables: CURSOR_ORES, cursorVeinCache, cursorLabelKey, cursorLabelSince
|
||||||
|
|
||||||
|
### src/ui/info.js (208 lines)
|
||||||
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, pipeContentsText
|
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel, pipeContentsText
|
||||||
|
|
||||||
@@ -513,9 +520,9 @@ Keyboard and mouse listeners.
|
|||||||
- functions: isTyping
|
- functions: isTyping
|
||||||
- variables: canvas
|
- variables: canvas
|
||||||
|
|
||||||
### src/ui/actions.js (254 lines)
|
### src/ui/actions.js (256 lines)
|
||||||
What input does: overlays, F, pick, copy / cut / paste, undo, building and tool use each frame. Which overlays are on, shown as a coloured frame round the screen and a named badge
|
What input does: overlays, F, pick, copy / cut / paste, undo, building and tool use each frame. Which overlays are on, shown as a coloured frame round the screen and a named badge
|
||||||
- functions: updateOverlayUI, toggleLens, doInteract, pickUnderCursor, aimBlock, stampOrigin, selModeHeld, startPaste, stopSelectAndPaste, plural, piecesIn, doUndo, endRmbRecord, dragDirFor, blockLabel, primaryPress, primaryRelease, secondaryPress, firePing, toolTick
|
- functions: updateOverlayUI, toggleLens, doInteract, pickUnderCursor, aimBlock, stampOrigin, selModeHeld, startPaste, stopSelectAndPaste, plural, piecesIn, doUndo, endRmbRecord, dragDirFor, blockLabel, primaryPress, primaryRelease, secondaryPress, pingWait, firePing, toolTick
|
||||||
- variables: ovShown, PING_RECHARGE
|
- variables: ovShown, PING_RECHARGE
|
||||||
|
|
||||||
### src/ui/gamepad.js (60 lines)
|
### src/ui/gamepad.js (60 lines)
|
||||||
@@ -546,7 +553,7 @@ Shared world colours, minimap and full map.
|
|||||||
- functions: resetMap, mapArea, updateMap, drawMap, mapFit, drawFullMap, openMap, zoomMap, zoomMinimap
|
- functions: resetMap, mapArea, updateMap, drawMap, mapFit, drawFullMap, openMap, zoomMap, zoomMinimap
|
||||||
- variables: MAP_S, MAP
|
- variables: MAP_S, MAP
|
||||||
|
|
||||||
### src/ui/loop.js (138 lines)
|
### src/ui/loop.js (140 lines)
|
||||||
The frame loop and boot.
|
The frame loop and boot.
|
||||||
- functions: scanVisible, frame, boot
|
- functions: scanVisible, frame, boot
|
||||||
- variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT
|
- variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT
|
||||||
@@ -562,6 +569,7 @@ The frame loop and boot.
|
|||||||
- `test/t_build.js`: Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
- `test/t_build.js`: Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
||||||
- `test/t_conveyors.js`: Screw conveyors that never carry liquid (it flows through), screw and belt throughput, filters.
|
- `test/t_conveyors.js`: Screw conveyors that never carry liquid (it flows through), screw and belt throughput, filters.
|
||||||
- `test/t_corner_seal.js`: Corner-touching square walls seal loose grains without changing free piles or worldgen.
|
- `test/t_corner_seal.js`: Corner-touching square walls seal loose grains without changing free piles or worldgen.
|
||||||
|
- `test/t_cursor.js`: Ore identification, cached connected veins, ping timing, settings and label lifetime.
|
||||||
- `test/t_dev_console.js`: Dev command validation, discovery, build costs, runtime controls and save metadata.
|
- `test/t_dev_console.js`: Dev command validation, discovery, build costs, runtime controls and save metadata.
|
||||||
- `test/t_dev_console.py`: Dev console confirmation, keyboard isolation, HUD, time controls and saved badge.
|
- `test/t_dev_console.py`: Dev console confirmation, keyboard isolation, HUD, time controls and saved badge.
|
||||||
- `test/t_diag_screws.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
- `test/t_diag_screws.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
"ui/selection.js",
|
"ui/selection.js",
|
||||||
"ui/selection-pickers.js",
|
"ui/selection-pickers.js",
|
||||||
"ui/selection-panels.js",
|
"ui/selection-panels.js",
|
||||||
|
"ui/cursor.js",
|
||||||
"ui/info.js",
|
"ui/info.js",
|
||||||
"ui/devconsole.js",
|
"ui/devconsole.js",
|
||||||
"ui/input.js",
|
"ui/input.js",
|
||||||
|
|||||||
@@ -167,6 +167,22 @@ function drawPing() {
|
|||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
// Edges are built once when the cursor enters a different vein.
|
||||||
|
function drawHoveredVein() {
|
||||||
|
if (SET.cursorLabel === 'off' || (!GAME.mouse.in && !GAME.usingPad)) return;
|
||||||
|
const vein = hoveredVein(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||||
|
if (!vein) return;
|
||||||
|
const ctx = R.ctx, zp = R.zp;
|
||||||
|
ctx.save(); ctx.beginPath(); ctx.rect(0, 0, R.canvas.width, R.canvas.height); ctx.clip();
|
||||||
|
ctx.strokeStyle = 'rgba(235,245,255,0.85)'; ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (const e of vein.edges) {
|
||||||
|
if (e[2] < R.vx0 || e[0] > R.vx0 + R.bw || e[3] < R.vy0 || e[1] > R.vy0 + R.bh) continue;
|
||||||
|
const a = worldToScreenPx(e[0], e[1]);
|
||||||
|
ctx.moveTo(a.x, a.y); ctx.lineTo(a.x + (e[2] - e[0]) * zp, a.y + (e[3] - e[1]) * zp);
|
||||||
|
}
|
||||||
|
ctx.stroke(); ctx.restore();
|
||||||
|
}
|
||||||
// While the ping recharges, a thin arc around the bot fills up (only with the drill in hand).
|
// While the ping recharges, a thin arc around the bot fills up (only with the drill in hand).
|
||||||
function drawPingRecharge() {
|
function drawPingRecharge() {
|
||||||
if (toolId() !== 'drill' || GAME.pingTick === undefined || tick < GAME.pingTick) return;
|
if (toolId() !== 'drill' || GAME.pingTick === undefined || tick < GAME.pingTick) return;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Hover work is shared by the cursor label and the vein outline.
|
||||||
|
const CURSOR_ORES = new Uint8Array(NMAT);
|
||||||
|
for (let m = 0; m < NMAT; m++) if (PING_VEINS[m]) { CURSOR_ORES[m] = 1; CURSOR_ORES[YLD[m]] = 1; }
|
||||||
|
function oreLike(m, bar) { return !!(bar || CURSOR_ORES[m] || SOLIDMETAL[m]); }
|
||||||
|
function bitNeeded(m) {
|
||||||
|
if (HARD[m] > 6) return 'cannot be drilled';
|
||||||
|
return 'needs ' + BIT_TIERS.find(b => b.hard >= HARD[m]).name.toLowerCase();
|
||||||
|
}
|
||||||
|
function pingVisible(x, y) {
|
||||||
|
const p = GAME.ping;
|
||||||
|
if (!p || x < 0 || y < 0 || x >= W || y >= H) return false;
|
||||||
|
if (!p.cellSet) p.cellSet = new Set(p.cells);
|
||||||
|
if (!p.cellSet.has(y * W + x)) return false;
|
||||||
|
const start = Math.hypot(x - p.x, y - p.y) / PING_SPEED * 1000, ms = performance.now() - p.t0;
|
||||||
|
return ms >= start && ms < start + PING_HOLD + PING_FADE;
|
||||||
|
}
|
||||||
|
function cursorVein(i) {
|
||||||
|
const m = mat[i], cells = new Set([i]), queue = [i], edges = [];
|
||||||
|
const same = q => q >= 0 && q < N && mat[q] === m && (flags[q] & F_NAT);
|
||||||
|
const add = n => { if (cells.size < 6000 && same(n) && !cells.has(n)) { cells.add(n); queue.push(n); } };
|
||||||
|
for (let k = 0; k < queue.length; k++) {
|
||||||
|
const q = queue[k], x = q % W;
|
||||||
|
if (x) add(q - 1);
|
||||||
|
if (x < W - 1) add(q + 1);
|
||||||
|
add(q - W); add(q + W);
|
||||||
|
}
|
||||||
|
for (const q of cells) {
|
||||||
|
const x = q % W, y = (q / W) | 0;
|
||||||
|
if (!x || !same(q - 1)) edges.push([x, y, x, y + 1]);
|
||||||
|
if (x === W - 1 || !same(q + 1)) edges.push([x + 1, y, x + 1, y + 1]);
|
||||||
|
if (!same(q - W)) edges.push([x, y, x + 1, y]);
|
||||||
|
if (!same(q + W)) edges.push([x, y + 1, x + 1, y + 1]);
|
||||||
|
}
|
||||||
|
return { cells, edges, m, world: mat };
|
||||||
|
}
|
||||||
|
let cursorVeinCache = null, cursorLabelKey = null, cursorLabelSince = 0;
|
||||||
|
function hoveredVein(x, y) {
|
||||||
|
if (x < 0 || y < 0 || x >= W || y >= H) return null;
|
||||||
|
const i = y * W + x, m = mat[i];
|
||||||
|
if (!PING_VEINS[m] || !(flags[i] & F_NAT) || (SET.fog && fogSeen(x, y) < 64 && !pingVisible(x, y))) return null;
|
||||||
|
if (!cursorVeinCache || cursorVeinCache.world !== mat || cursorVeinCache.m !== m || !cursorVeinCache.cells.has(i)) cursorVeinCache = cursorVein(i);
|
||||||
|
return cursorVeinCache;
|
||||||
|
}
|
||||||
|
function cursorLabelAlpha(key, now) {
|
||||||
|
if (key !== cursorLabelKey) { cursorLabelKey = key; cursorLabelSince = now; }
|
||||||
|
return Math.max(0, Math.min(1, (2400 - (now - cursorLabelSince)) / 400));
|
||||||
|
}
|
||||||
+15
-8
@@ -77,7 +77,8 @@ function reactionsOf(m, forNotebook) {
|
|||||||
// What is under the cursor: t1 is the name, t2 the details, warn a short problem
|
// What is under the cursor: t1 is the name, t2 the details, warn a short problem
|
||||||
// (too hard, out of reach) for the label next to the cursor.
|
// (too hard, out of reach) for the label next to the cursor.
|
||||||
function hoverInfo(ax, ay) {
|
function hoverInfo(ax, ay) {
|
||||||
if (SET.fog && fogSeen(ax, ay) < 64) return { t1: 'Unexplored', t2: [], t3: [] };
|
const pinged = pingVisible(ax, ay);
|
||||||
|
if (SET.fog && fogSeen(ax, ay) < 64 && !pinged) return { t1: 'Unexplored', t2: [], t3: [] };
|
||||||
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return { t1: '', t2: ['Outside the world'], warn: '', air: true };
|
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return { t1: '', t2: ['Outside the world'], warn: '', air: true };
|
||||||
const i = ay * W + ax, m = mat[i];
|
const i = ay * W + ax, m = mat[i];
|
||||||
let warn = '';
|
let warn = '';
|
||||||
@@ -143,8 +144,8 @@ function hoverInfo(ax, ay) {
|
|||||||
if (player.lens && !bar) t2.push(fmtT(temp[i]));
|
if (player.lens && !bar) t2.push(fmtT(temp[i]));
|
||||||
if (!bar && m !== EMPTY && PH[m] === PH_SOLID && !PARTMAT[m] && (flags[i] & F_NAT)) {
|
if (!bar && m !== EMPTY && PH[m] === PH_SOLID && !PARTMAT[m] && (flags[i] & F_NAT)) {
|
||||||
const ok = HARD[m] <= BIT_TIERS[player.bitTier].hard;
|
const ok = HARD[m] <= BIT_TIERS[player.bitTier].hard;
|
||||||
t2.push(HARD[m] > 6 ? 'cannot be drilled' : ok ? `drillable (hardness ${HARD[m]})` : `too hard (${HARD[m]})`);
|
t2.push(HARD[m] > 6 ? 'cannot be drilled' : ok ? `drillable (hardness ${HARD[m]})` : bitNeeded(m));
|
||||||
if (!ok && toolId() === 'drill') warn = HARD[m] > 6 ? 'cannot be drilled' : 'too hard';
|
if (!ok && toolId() === 'drill') warn = bitNeeded(m);
|
||||||
}
|
}
|
||||||
if (!bar && PH[m] === PH_POWDER) t2.push('loose');
|
if (!bar && PH[m] === PH_POWDER) t2.push('loose');
|
||||||
if (!id && !bar && m === EMPTY && bgm[i]) { t1 = bgm[i] === WOOD ? 'Tree trunk' : 'Leaves'; t2.unshift(bgm[i] === WOOD ? 'drill it for wood chunks' : 'water and sand pass through'); }
|
if (!id && !bar && m === EMPTY && bgm[i]) { t1 = bgm[i] === WOOD ? 'Tree trunk' : 'Leaves'; t2.unshift(bgm[i] === WOOD ? 'drill it for wood chunks' : 'water and sand pass through'); }
|
||||||
@@ -163,7 +164,8 @@ function hoverInfo(ax, ay) {
|
|||||||
// what it does, unless the cursor is on a machine or mold, which have their own details
|
// what it does, unless the cursor is on a machine or mold, which have their own details
|
||||||
const what = bar ? bar.m : !mo && !id && !(p && p !== P_GRATE && p !== P_PIPE) && m !== BELTL && m !== BELTR && m !== PIPE ? (m === EMPTY && bgm[i] ? bgm[i] : m) : 0;
|
const what = bar ? bar.m : !mo && !id && !(p && p !== P_GRATE && p !== P_PIPE) && m !== BELTL && m !== BELTR && m !== PIPE ? (m === EMPTY && bgm[i] ? bgm[i] : m) : 0;
|
||||||
const t3 = what ? reactionsOf(what) : [];
|
const t3 = what ? reactionsOf(what) : [];
|
||||||
return { t1, t2, t3, warn, contents, air: m === EMPTY && !bgm[i] && !bar && !mo && !id && !p && !b };
|
if (pinged) t2.push('found by the ping');
|
||||||
|
return { t1, t2, t3, warn, contents, ore: oreLike(m, bar), m: bar ? bar.m : m, air: m === EMPTY && !bgm[i] && !bar && !mo && !id && !p && !b };
|
||||||
}
|
}
|
||||||
function renderInfo() {
|
function renderInfo() {
|
||||||
const r = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
const r = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||||
@@ -173,15 +175,19 @@ function renderInfo() {
|
|||||||
}
|
}
|
||||||
// Name of what is under the cursor, drawn right next to it
|
// Name of what is under the cursor, drawn right next to it
|
||||||
function drawCursorLabel(px, py, r) {
|
function drawCursorLabel(px, py, r) {
|
||||||
if (!SET.cursorLabel || (!GAME.mouse.in && !GAME.usingPad)) return;
|
if (SET.cursorLabel === 'off' || (!GAME.mouse.in && !GAME.usingPad)) { cursorLabelKey = null; return; }
|
||||||
const info = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
const info = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||||
if (!info.t1 || info.air) return;
|
if (!info.t1 || (info.air && !info.warn) || (SET.cursorLabel === 'ores' && !info.ore && !info.warn)) { cursorLabelKey = null; return; }
|
||||||
|
const vein = hoveredVein(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||||
|
const alpha = cursorLabelAlpha(vein || info.m || info.t1, performance.now());
|
||||||
|
if (!alpha && !info.warn) return;
|
||||||
const ctx = R.ctx, dpr = R.canvas.width / window.innerWidth;
|
const ctx = R.ctx, dpr = R.canvas.width / window.innerWidth;
|
||||||
const fs = Math.round(12 * dpr * SET.uiScale);
|
const fs = Math.round(12 * dpr * SET.uiScale);
|
||||||
if (!R.uiFont) R.uiFont = getComputedStyle(document.documentElement).getPropertyValue('--font-ui').trim() || 'sans-serif';
|
if (!R.uiFont) R.uiFont = getComputedStyle(document.documentElement).getPropertyValue('--font-ui').trim() || 'sans-serif';
|
||||||
const main = info.t1 + (info.contents && info.warn ? ` ${info.contents}` : '');
|
const main = alpha ? info.t1 + (info.t2.includes('found by the ping') ? ' · found by the ping' : '') + (info.contents && info.warn ? ` ${info.contents}` : '') : '';
|
||||||
const sub = info.warn || info.contents || (R.lens ? info.t2.find(t => /°C$/.test(t)) || '' : '');
|
const sub = info.warn || info.contents || (R.lens ? info.t2.find(t => /°C$/.test(t)) || '' : '');
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
ctx.globalAlpha = info.warn ? 1 : alpha;
|
||||||
ctx.font = `600 ${fs}px ${R.uiFont}`;
|
ctx.font = `600 ${fs}px ${R.uiFont}`;
|
||||||
const w1 = ctx.measureText(main).width;
|
const w1 = ctx.measureText(main).width;
|
||||||
ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`;
|
ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`;
|
||||||
@@ -193,7 +199,8 @@ function drawCursorLabel(px, py, r) {
|
|||||||
ctx.fillStyle = 'rgba(20,17,15,0.82)';
|
ctx.fillStyle = 'rgba(20,17,15,0.82)';
|
||||||
ctx.beginPath(); ctx.roundRect(x, y, w1 + w2 + padX * 2, h, 4 * dpr); ctx.fill();
|
ctx.beginPath(); ctx.roundRect(x, y, w1 + w2 + padX * 2, h, 4 * dpr); ctx.fill();
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.font = `600 ${fs}px ${R.uiFont}`; ctx.fillStyle = '#efe6d8'; ctx.fillText(main, x + padX, y + h / 2);
|
ctx.font = `600 ${fs}px ${R.uiFont}`; ctx.fillStyle = '#efe6d8'; ctx.globalAlpha = alpha; ctx.fillText(main, x + padX, y + h / 2);
|
||||||
|
ctx.globalAlpha = info.warn ? 1 : alpha;
|
||||||
if (sub) { ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`; ctx.fillStyle = info.warn ? '#ef6a55' : info.contents ? '#efe6d8' : '#ffd27a'; ctx.fillText(sub, x + padX + w1 + fs * 0.6, y + h / 2); }
|
if (sub) { ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`; ctx.fillStyle = info.warn ? '#ef6a55' : info.contents ? '#efe6d8' : '#ffd27a'; ctx.fillText(sub, x + padX + w1 + fs * 0.6, y + h / 2); }
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ function frame(now) {
|
|||||||
if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); }
|
if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); }
|
||||||
drawJoinMarks();
|
drawJoinMarks();
|
||||||
drawPing();
|
drawPing();
|
||||||
|
drawHoveredVein();
|
||||||
drawPingRecharge();
|
drawPingRecharge();
|
||||||
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
||||||
drawFloaters();
|
drawFloaters();
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ const SET_TABS = {
|
|||||||
{ k: 'uiScale', label: 'Interface size', type: 'range', min: 0.8, max: 1.4, step: 0.05, fmt: v => Math.round(v * 100) + '%' },
|
{ k: 'uiScale', label: 'Interface size', type: 'range', min: 0.8, max: 1.4, step: 0.05, fmt: v => Math.round(v * 100) + '%' },
|
||||||
{ k: 'fpsCap', label: 'Frame cap', d: 'Uncapped follows your display refresh rate.', type: 'select', opts: [[30, '30 fps'], [60, '60 fps'], [120, '120 fps'], [0, 'Uncapped']] },
|
{ k: 'fpsCap', label: 'Frame cap', d: 'Uncapped follows your display refresh rate.', type: 'select', opts: [[30, '30 fps'], [60, '60 fps'], [120, '120 fps'], [0, 'Uncapped']] },
|
||||||
{ k: 'fullscreen', label: 'Fullscreen', type: 'button', text: 'Toggle fullscreen', fn: toggleFullscreen },
|
{ k: 'fullscreen', label: 'Fullscreen', type: 'button', text: 'Toggle fullscreen', fn: toggleFullscreen },
|
||||||
{ k: 'cursorLabel', label: 'Name next to the cursor', d: 'Shows what you are pointing at right beside the cursor.', type: 'toggle' },
|
{ k: 'cursorLabel', label: 'Name next to the cursor', d: 'Briefly names ores and metals, or everything. Warnings stay visible.', type: 'select', opts: [['off', 'Off'], ['ores', 'Ores only'], ['all', 'Everything']] },
|
||||||
{ k: 'minimap', label: 'Show minimap', type: 'toggle' },
|
{ k: 'minimap', label: 'Show minimap', type: 'toggle' },
|
||||||
{ k: 'showPerf', label: 'Performance stats', d: 'Frame time, simulation time and active areas.', type: 'toggle' },
|
{ k: 'showPerf', label: 'Performance stats', d: 'Frame time, simulation time and active areas.', type: 'toggle' },
|
||||||
{ k: 'cameraSmooth', label: 'Camera smoothing', type: 'range', min: 0, max: 0.95, step: 0.05, fmt: v => Math.round(v * 100) + '%' },
|
{ k: 'cameraSmooth', label: 'Camera smoothing', type: 'range', min: 0, max: 0.95, step: 0.05, fmt: v => Math.round(v * 100) + '%' },
|
||||||
|
|||||||
+2
-1
@@ -7,13 +7,14 @@ const SET_DEFAULT = {
|
|||||||
look: 'smooth', lighting: 'full', lightEvery: 1, ambient: 0.12, daylight: 1, glow: 'low', grain: 1, incandescence: true, particles: true,
|
look: 'smooth', lighting: 'full', lightEvery: 1, ambient: 0.12, daylight: 1, glow: 'low', grain: 1, incandescence: true, particles: true,
|
||||||
minimap: true, minimapZoom: 4, fog: true, thermalPalette: 'inferno', simSpeed: 1, maxSteps: 3,
|
minimap: true, minimapZoom: 4, fog: true, thermalPalette: 'inferno', simSpeed: 1, maxSteps: 3,
|
||||||
volMaster: 0.8, volMusic: 0.5, volSfx: 0.8, muted: false, muteUnfocused: true,
|
volMaster: 0.8, volMusic: 0.5, volSfx: 0.8, muted: false, muteUnfocused: true,
|
||||||
deadzone: 0.18, aimSpeed: 1, autosave: 3, cardSeconds: 9, cursorLabel: true, sv: 2,
|
deadzone: 0.18, aimSpeed: 1, autosave: 3, cardSeconds: 9, cursorLabel: 'ores', sv: 2,
|
||||||
};
|
};
|
||||||
const SET = Object.assign({}, SET_DEFAULT);
|
const SET = Object.assign({}, SET_DEFAULT);
|
||||||
// sv: settings version. 2: the default zoom became fully zoomed in, so older saved settings move there once.
|
// sv: settings version. 2: the default zoom became fully zoomed in, so older saved settings move there once.
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
try {
|
try {
|
||||||
const s = JSON.parse(localStorage.getItem('deepgrain.settings') || '{}'); Object.assign(SET, s);
|
const s = JSON.parse(localStorage.getItem('deepgrain.settings') || '{}'); Object.assign(SET, s);
|
||||||
|
if (typeof SET.cursorLabel === 'boolean') SET.cursorLabel = SET.cursorLabel ? 'ores' : 'off';
|
||||||
if (!(s.sv >= 2)) { SET.zoom = SET_DEFAULT.zoom; SET.sv = 2; if (Object.keys(s).length) saveSettings(); }
|
if (!(s.sv >= 2)) { SET.zoom = SET_DEFAULT.zoom; SET.sv = 2; if (Object.keys(s).length) saveSettings(); }
|
||||||
} catch (e) { /* storage unavailable */ }
|
} catch (e) { /* storage unavailable */ }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1215,7 +1215,7 @@ with sync_playwright() as p:
|
|||||||
fp.wait_for_function('GAME.running')
|
fp.wait_for_function('GAME.running')
|
||||||
# Silo feedback uses real pointer positions; drawing checks sample an isolated canvas.
|
# Silo feedback uses real pointer positions; drawing checks sample an isolated canvas.
|
||||||
fp.evaluate("""() => {
|
fp.evaluate("""() => {
|
||||||
GAME.paused = true; SET.fog = false; SET.look = 'pixel'; SET.zoom = 8; SET.cursorLabel = true;
|
GAME.paused = true; SET.fog = false; SET.look = 'pixel'; SET.zoom = 8; SET.cursorLabel = 'all';
|
||||||
SET.cameraLead = false; SET.cameraSmooth = 0; resizeRender();
|
SET.cameraLead = false; SET.cameraSmooth = 0; resizeRender();
|
||||||
for (let y = 80; y < 140; y++) for (let x = 80; x < 160; x++) { setCell(y * W + x, EMPTY, 20, 0); bgm[y * W + x] = 0; }
|
for (let y = 80; y < 140; y++) for (let x = 80; x < 160; x++) { setCell(y * W + x, EMPTY, 20, 0); bgm[y * W + x] = 0; }
|
||||||
window.testSilo = addMachine('silo', 28, 25, 0);
|
window.testSilo = addMachine('silo', 28, 25, 0);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Ore identification, cached connected veins, ping timing, settings and label lifetime.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { load, suite, flatWorld } = require('./harness');
|
||||||
|
const { run } = load();
|
||||||
|
const { check, done } = suite();
|
||||||
|
run(`const localStorage = { getItem: () => '{}', setItem() {} };`);
|
||||||
|
for (const f of ['ui/state.js', 'ui/hud.js', 'ui/cursor.js', 'ui/info.js', 'render/overlays.js']) run(fs.readFileSync(path.join(__dirname, '../src', f), 'utf8'));
|
||||||
|
flatWorld(run, 160, 160, 150);
|
||||||
|
for (const m of ['CUV', 'CUORE', 'COALV', 'COAL', 'FEV', 'FEORE', 'COPPER'])
|
||||||
|
check(m + ' is ore-like', run(`oreLike(${m}, null)`));
|
||||||
|
check('sand and stone are not ore-like', run('!oreLike(SAND, null) && !oreLike(STONE, null)'));
|
||||||
|
check('any hovered bar is ore-like', run('oreLike(EMPTY, { m: BARGL })'));
|
||||||
|
check('default labels are ores only', run("SET.cursorLabel === 'ores'"));
|
||||||
|
for (const [saved, expected] of [[true, 'ores'], [false, 'off'], ['all', 'all']]) {
|
||||||
|
run(`localStorage.getItem = () => '${JSON.stringify({cursorLabel: saved, sv: 2})}'; loadSettings();`);
|
||||||
|
check('saved ' + saved + ' becomes ' + expected, run(`SET.cursorLabel === '${expected}'`));
|
||||||
|
}
|
||||||
|
check('bit text names the first tier that can drill it', run("BIT_TIERS.every(b => { const m = Array.from(HARD).findIndex(h => h === b.hard); return m < 0 || bitNeeded(m) === 'needs ' + b.name.toLowerCase(); })"));
|
||||||
|
check('undrillable material keeps its warning', run("bitNeeded(Array.from(HARD).findIndex(h => h > 6)) === 'cannot be drilled'"));
|
||||||
|
run(`
|
||||||
|
SET.fog = false; GAME.tool = 0;
|
||||||
|
setCell(20 * W + 20, CUV, 22, F_NAT);
|
||||||
|
setCell(20 * W + 21, CUV, 22, F_NAT);
|
||||||
|
setCell(21 * W + 21, CUV, 22, F_NAT);
|
||||||
|
setCell(21 * W + 20, FEV, 22, F_NAT);
|
||||||
|
setCell(20 * W + 22, CUV, 22, 0);
|
||||||
|
setCell(22 * W + 22, CUV, 22, F_NAT);
|
||||||
|
const v = hoveredVein(20, 20);
|
||||||
|
`);
|
||||||
|
check('fill follows four neighbours of the same natural material', run('v.cells.size === 3 && v.edges.length === 8'));
|
||||||
|
check('moving within the same vein reuses cells and edges', run('hoveredVein(21, 21) === v'));
|
||||||
|
check('leaving the vein removes the outline', run('hoveredVein(22, 20) === null'));
|
||||||
|
check('a disconnected vein gets a new cache', run('hoveredVein(22, 22) !== v'));
|
||||||
|
run(`for (let y = 30; y < 130; y++) for (let x = 0; x < 100; x++) setCell(y * W + x, CUV, 22, F_NAT);`);
|
||||||
|
check('a large vein stops at 6000 cells', run('cursorVein(30 * W).cells.size === 6000'));
|
||||||
|
run(`setCell(W - 1, CUV, 22, F_NAT); setCell(W, CUV, 22, F_NAT);`);
|
||||||
|
check('fill does not wrap across world edges', run('cursorVein(W - 1).cells.size === 1'));
|
||||||
|
run(`setCell(20 * W + 20, FEV, 22, F_NAT);`);
|
||||||
|
check('hover details and warning name the needed bit', run("hoverInfo(20, 20).t2.includes(bitNeeded(FEV)) && hoverInfo(20, 20).warn === bitNeeded(FEV)"));
|
||||||
|
run(`
|
||||||
|
SET.fog = true; fog.fill(0);
|
||||||
|
const realNow = performance.now.bind(performance); let hoverNow = 1000;
|
||||||
|
performance.now = () => hoverNow;
|
||||||
|
GAME.ping = { x: 4, y: 20, t0: 1000, cells: [20 * W + 20] };
|
||||||
|
`);
|
||||||
|
check('fog hides a vein before the ping reaches it', run("hoverInfo(20, 20).t1 === 'Unexplored' && !hoveredVein(20, 20)"));
|
||||||
|
run('hoverNow = 1100;');
|
||||||
|
check('a ping names a hidden vein at arrival', run("hoverInfo(20, 20).t1 === MD[FEV].name && hoverInfo(20, 20).t2.includes('found by the ping') && !!hoveredVein(20, 20)"));
|
||||||
|
run('const pingSet = GAME.ping.cellSet; hoverNow = 3099;');
|
||||||
|
check('ping cells are cached through the fade', run('pingVisible(20, 20) && GAME.ping.cellSet === pingSet && !pingVisible(21, 20)'));
|
||||||
|
run('hoverNow = 3100;');
|
||||||
|
check('fog hides the vein again at the end of the fade', run("hoverInfo(20, 20).t1 === 'Unexplored' && !hoveredVein(20, 20)"));
|
||||||
|
run('performance.now = realNow;');
|
||||||
|
check('the name stays fully visible for two seconds', run("cursorLabelAlpha('copper', 100) === 1 && cursorLabelAlpha('copper', 2100) === 1"));
|
||||||
|
check('the name fades over 400 ms', run("cursorLabelAlpha('copper', 2300) === 0.5 && cursorLabelAlpha('copper', 2500) === 0"));
|
||||||
|
check('a different thing brings the label back', run("cursorLabelAlpha('iron', 2600) === 1"));
|
||||||
|
done();
|
||||||
@@ -76,6 +76,58 @@ with sync_playwright() as p:
|
|||||||
check('built solids get no loose-grain outline', ground['built'][0] == ground['built'][1], ground['built'])
|
check('built solids get no loose-grain outline', ground['built'][0] == ground['built'][1], ground['built'])
|
||||||
check('smooth: a thin darker seam separates ground and rubble', ground['smooth'][0] < ground['smooth'][1] * 0.9, ground['smooth'])
|
check('smooth: a thin darker seam separates ground and rubble', ground['smooth'][0] < ground['smooth'][1] * 0.9, ground['smooth'])
|
||||||
check('smooth: packed rubble keeps pebble shading', ground['texture'] > 30, ground['texture'])
|
check('smooth: packed rubble keeps pebble shading', ground['texture'] > 30, ground['texture'])
|
||||||
|
# Cursor labels and outlines use a paused scene and the real drawing functions.
|
||||||
|
cursor = ev("""() => {
|
||||||
|
SET.look='pixel'; SET.cursorLabel='ores'; SET.fog=false; SET.zoom=8;
|
||||||
|
resizeRender(); GAME.tool=0; GAME.mouse.in=true; GAME.usingPad=false;
|
||||||
|
bot.x=118; bot.y=114; snapCamera(); player.lens=false; R.lens=false;
|
||||||
|
for(let y=110;y<130;y++)for(let x=110;x<140;x++) {
|
||||||
|
const i=y*W+x; setCell(i,STONE,22,F_NAT); part[i]=0; mach[i]=0; occ[i]=0; bgm[i]=0;
|
||||||
|
blockP[(y>>2)*BW+(x>>2)]=0;
|
||||||
|
}
|
||||||
|
for(let y=118;y<126;y++)for(let x=120;x<132;x++)
|
||||||
|
if(x+y>239 && x+y<255)setCell(y*W+x,CUV,22,F_NAT);
|
||||||
|
setCell(116*W+116,SAND,22,0);
|
||||||
|
GAME.aim={x:124,y:122}; cursorLabelKey=null;
|
||||||
|
const ctx=R.ctx,text=ctx.fillText,stroke=ctx.stroke,now=performance.now.bind(performance);
|
||||||
|
let time=1000,calls=[],strokes=0;
|
||||||
|
performance.now=()=>time;
|
||||||
|
ctx.fillText=function(t){calls.push([t,this.globalAlpha,this.fillStyle]);};
|
||||||
|
ctx.stroke=function(){strokes++;};
|
||||||
|
const label=()=>{calls=[];drawCursorLabel(500,300,1);return calls.slice();};
|
||||||
|
try {
|
||||||
|
GAME.aim={x:116,y:116};const sand=label();
|
||||||
|
GAME.aim={x:124,y:122};const copper=label();drawHoveredVein();const outlined=strokes;
|
||||||
|
const vein=cursorVeinCache;
|
||||||
|
GAME.aim={x:125,y:122};time=3200;const fading=label();
|
||||||
|
const reused=cursorVeinCache===vein;
|
||||||
|
time=3400;const faded=label();
|
||||||
|
setCell(116*W+117,FEV,22,F_NAT); GAME.aim={x:117,y:116};
|
||||||
|
const hard=label();time=6000;const warning=label();
|
||||||
|
GAME.aim={x:124,y:122}; SET.fog=true;fog.fill(0);
|
||||||
|
GAME.ping={x:124,y:122,t0:6000,r:48,cells:[122*W+124]};
|
||||||
|
const ping=hoverInfo(124,122),pingLabel=label();renderInfo();
|
||||||
|
const panel=$('info').textContent;
|
||||||
|
time=8000;const hidden=hoverInfo(124,122).t1;
|
||||||
|
GAME.ping=null;SET.fog=false; SET.cursorLabel='off';
|
||||||
|
const off=label();strokes=0;drawHoveredVein();
|
||||||
|
return {sand,copper,outlined,reused,fading,faded,hard,warning,ping,pingLabel,panel,hidden,off,offStrokes:strokes};
|
||||||
|
} finally {ctx.fillText=text;ctx.stroke=stroke;performance.now=now;}
|
||||||
|
}""")
|
||||||
|
check('ores mode skips sand and draws the copper vein name', not cursor['sand'] and any(t[0] == 'Copper ore vein' for t in cursor['copper']), cursor['copper'])
|
||||||
|
check('hovering a vein draws an outline and reuses it within the vein', cursor['outlined'] > 0 and cursor['reused'])
|
||||||
|
check('the name fades then disappears while staying on the vein', any(0 < t[1] < 1 for t in cursor['fading']) and not cursor['faded'], cursor['fading'])
|
||||||
|
check('the needed bit stays red after the name fades', any(t[0].startswith('needs ') and t[1] == 1 and t[2] == '#ef6a55' for t in cursor['warning']), cursor['warning'])
|
||||||
|
check('a pinged vein under fog names itself in the label and corner panel', cursor['ping']['t1'] == 'Copper ore vein' and 'found by the ping' in cursor['panel'] and any('found by the ping' in t[0] for t in cursor['pingLabel']), cursor['pingLabel'])
|
||||||
|
check('a faded ping hides the vein again', cursor['hidden'] == 'Unexplored')
|
||||||
|
check('off hides both label and vein outline', not cursor['off'] and cursor['offStrokes'] == 0)
|
||||||
|
for look in ['pixel', 'smooth']:
|
||||||
|
ev("""look => {
|
||||||
|
SET.look=look; SM.scale=1; SET.cursorLabel='ores'; GAME.aim={x:124,y:122}; cursorLabelKey=null;
|
||||||
|
$('cards').replaceChildren(); renderInfo(); drawWorld(); drawHoveredVein(); drawBot();
|
||||||
|
const p=worldToScreenPx(124,122); drawCursorLabel(p.x,p.y,1);
|
||||||
|
}""", look)
|
||||||
|
pg.screenshot(path=str(root / 'shots' / ('t77-vein.png' if look == 'pixel' else 't77-vein-smooth.png')))
|
||||||
wall = pg.evaluate('''() => {
|
wall = pg.evaluate('''() => {
|
||||||
GAME.paused = true; R.lens = false; SET.fog = false;
|
GAME.paused = true; R.lens = false; SET.fog = false;
|
||||||
SET.lighting = 'off'; SET.glow = 'off'; SET.grain = false;
|
SET.lighting = 'off'; SET.glow = 'off'; SET.grain = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user