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:
@@ -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
|
||||
// (too hard, out of reach) for the label next to the cursor.
|
||||
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 };
|
||||
const i = ay * W + ax, m = mat[i];
|
||||
let warn = '';
|
||||
@@ -143,8 +144,8 @@ function hoverInfo(ax, ay) {
|
||||
if (player.lens && !bar) t2.push(fmtT(temp[i]));
|
||||
if (!bar && m !== EMPTY && PH[m] === PH_SOLID && !PARTMAT[m] && (flags[i] & F_NAT)) {
|
||||
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]})`);
|
||||
if (!ok && toolId() === 'drill') warn = HARD[m] > 6 ? 'cannot be drilled' : 'too hard';
|
||||
t2.push(HARD[m] > 6 ? 'cannot be drilled' : ok ? `drillable (hardness ${HARD[m]})` : bitNeeded(m));
|
||||
if (!ok && toolId() === 'drill') warn = bitNeeded(m);
|
||||
}
|
||||
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'); }
|
||||
@@ -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
|
||||
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) : [];
|
||||
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() {
|
||||
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
|
||||
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));
|
||||
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 fs = Math.round(12 * dpr * SET.uiScale);
|
||||
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)) || '' : '');
|
||||
ctx.save();
|
||||
ctx.globalAlpha = info.warn ? 1 : alpha;
|
||||
ctx.font = `600 ${fs}px ${R.uiFont}`;
|
||||
const w1 = ctx.measureText(main).width;
|
||||
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.beginPath(); ctx.roundRect(x, y, w1 + w2 + padX * 2, h, 4 * dpr); ctx.fill();
|
||||
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); }
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ function frame(now) {
|
||||
if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); }
|
||||
drawJoinMarks();
|
||||
drawPing();
|
||||
drawHoveredVein();
|
||||
drawPingRecharge();
|
||||
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
||||
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: '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: '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: '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) + '%' },
|
||||
|
||||
+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,
|
||||
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,
|
||||
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);
|
||||
// sv: settings version. 2: the default zoom became fully zoomed in, so older saved settings move there once.
|
||||
function loadSettings() {
|
||||
try {
|
||||
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(); }
|
||||
} catch (e) { /* storage unavailable */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user