"use strict"; function renderStats() { const alive = world.tarinai; const avg = (fn) => alive.length ? alive.reduce((a, t) => a + fn(t), 0) / alive.length : 0; const environment = world.environmentScores ? world.environmentScores() : { security: 100, hygiene: 100, happiness: 100 }; const values = { pop: alive.length, dead: world.deadCount, hunger: avg(t => t.hunger), lonely: avg(t => t.loneliness), stress: avg(t => t.stress), mood: avg(t => t.mood), security: environment.security, hygiene: environment.hygiene, happiness: environment.happiness, objects: { ...(world.itemCounts || {}) }, }; setTextIfChanged(ui.statPop, "pop", values.pop); setTextIfChanged(ui.statDead, "dead", values.dead); setTextIfChanged(ui.statColonyMood, "colonyMood", world.colonyMood?.label || "のんびり"); updateColonyHistory(values); drawColonyChart(values); renderSelected(); if (world.familyTreeDirty) scheduleArchiveWindowRender(); } function updateColonyHistory(values) { const t = Math.max(0, world.time || 0); const history = uiCache.chartHistory; const interval = Math.max(1, (CONFIG.dayLength || 120) / 12); const maxPoints = 5 * 12; if ((history.length && t < history[history.length - 1].t) || (Number.isFinite(uiCache.chartBucketStart) && t < uiCache.chartBucketStart)) { history.length = 0; uiCache.chartBucketStart = -Infinity; uiCache.chartBucketEnd = -Infinity; uiCache.chartBucketAccum = null; uiCache.environmentInitialRow = null; uiCache.lastChartDraw = ""; } const objectKeys = ["grass", "zunchi", "trace"]; const makeBucket = (start) => ({ start, end: start + interval, count: 0, sums: { pop: 0, hunger: 0, lonely: 0, stress: 0, mood: 0, security: 0, hygiene: 0, happiness: 0 }, objects: Object.fromEntries(objectKeys.map(k => [k, 0])), }); if (!uiCache.chartBucketAccum) { const start = Math.floor(t / interval) * interval; uiCache.chartBucketStart = start; uiCache.chartBucketEnd = start + interval; uiCache.chartBucketAccum = makeBucket(start); } const pushBucket = (bucket) => { if (!bucket || bucket.count <= 0) return; const row = { t: bucket.end, pop: bucket.sums.pop / bucket.count, hunger: bucket.sums.hunger / bucket.count, lonely: bucket.sums.lonely / bucket.count, stress: bucket.sums.stress / bucket.count, mood: bucket.sums.mood / bucket.count, security: bucket.sums.security / bucket.count, hygiene: bucket.sums.hygiene / bucket.count, happiness: bucket.sums.happiness / bucket.count, ...Object.fromEntries(objectKeys.map(k => [`obj_${k}`, bucket.objects[k] / bucket.count])), }; history.push(row); if (history.length > maxPoints) history.splice(0, history.length - maxPoints); }; while (t >= uiCache.chartBucketEnd) { const b = uiCache.chartBucketAccum; pushBucket(b); uiCache.chartBucketStart = uiCache.chartBucketEnd; uiCache.chartBucketEnd = uiCache.chartBucketStart + interval; uiCache.chartBucketAccum = makeBucket(uiCache.chartBucketStart); } const bucket = uiCache.chartBucketAccum; bucket.count += 1; bucket.sums.pop += values.pop || 0; bucket.sums.hunger += values.hunger || 0; bucket.sums.lonely += values.lonely || 0; bucket.sums.stress += values.stress || 0; bucket.sums.mood += values.mood || 0; bucket.sums.security += values.security || 0; bucket.sums.hygiene += values.hygiene || 0; bucket.sums.happiness += values.happiness || 0; for (const k of objectKeys) bucket.objects[k] += (values.objects || {})[k] || 0; } function chartRows(values) { const bucket = uiCache.chartBucketAccum; const objectKeys = ["grass", "zunchi", "trace"]; const preview = bucket && bucket.count > 0 ? { t: Math.max(world.time || 0, bucket.start || 0), pop: bucket.sums.pop / bucket.count, hunger: bucket.sums.hunger / bucket.count, lonely: bucket.sums.lonely / bucket.count, stress: bucket.sums.stress / bucket.count, mood: bucket.sums.mood / bucket.count, security: bucket.sums.security / bucket.count, hygiene: bucket.sums.hygiene / bucket.count, happiness: bucket.sums.happiness / bucket.count, ...Object.fromEntries(objectKeys.map(k => [`obj_${k}`, bucket.objects[k] / bucket.count])), } : null; return uiCache.chartHistory.length ? [...uiCache.chartHistory, ...(preview ? [preview] : [])] : [preview || { t: world.time || 0, ...values }]; } function drawColonyChart(values) { const chart = ui.colonyChart; if (!chart) return; const now = performance.now(); const perf = world.performanceLevel ? world.performanceLevel() : 0; const minInterval = perf >= 3 ? 2600 : perf === 2 ? 1800 : perf === 1 ? 950 : 0; if (minInterval && uiCache.lastChartDrawAt && now - uiCache.lastChartDrawAt < minInterval) return; const mode = uiCache.chartMode || "population"; if (mode === "environment" && !uiCache.chartHistory.length && !uiCache.environmentInitialRow) { uiCache.environmentInitialRow = { t: world.time || 0, ...values }; } const rows = mode === "environment" ? (uiCache.chartHistory.length ? uiCache.chartHistory : [uiCache.environmentInitialRow || { t: world.time || 0, ...values }]) : chartRows(values); const last = rows[rows.length - 1] || values; const series = chartSeries(mode); const seriesSnapshot = series.map(s => `${s.key}:${Math.round(Number(last[s.key] || 0) * 10)}`).join(","); const snapshot = `${mode}:${rows.length}:${seriesSnapshot}:${chart.clientWidth}x${chart.clientHeight}`; if (uiCache.lastChartDraw === snapshot) return; uiCache.lastChartDraw = snapshot; uiCache.lastChartDrawAt = now; const cssW = Math.max(280, Math.floor(chart.clientWidth || chart.offsetWidth || 320)); const cssH = Math.max(160, Math.floor(chart.clientHeight || chart.offsetHeight || 188)); if (mode === "environment") return drawEnvironmentChart(chart, rows, cssW, cssH); const pad = { l: 30, r: 12, t: 12, b: 24 }; const w = cssW - pad.l - pad.r; const h = cssH - pad.t - pad.b; const maxVal = Math.max(10, ...series.flatMap(s => rows.map(r => Number(r[s.key] || 0)))); const xFor = (i) => rows.length <= 1 ? pad.l + w : pad.l + (i / (rows.length - 1)) * w; const yFor = (row, s) => { const raw = Number(row[s.key] || 0); const scale = s.percent ? 100 : maxVal; const v = clamp(raw / Math.max(scale, 1) * 100, 0, 100); return pad.t + h - h * v / 100; }; const grid = [0, 25, 50, 75, 100].map(yv => { const y = pad.t + h - h * yv / 100; return `${yv}`; }).join(""); const lines = series.map(s => { const pts = rows.map((row, i) => `${xFor(i).toFixed(1)},${yFor(row, s).toFixed(1)}`).join(" "); const last = rows[rows.length - 1]; const cx = xFor(rows.length - 1).toFixed(1); const cy = yFor(last, s).toFixed(1); return ``; }).join(""); chart.innerHTML = ``; if (ui.colonyChartLegend) ui.colonyChartLegend.innerHTML = series.map(s => `${s.label} ${formatChartValue(s.key, rows[rows.length - 1] || values)}`).join(""); } function drawEnvironmentChart(chart, rows, cssW, cssH) { const series = chartSeries("environment"); const row = rows[rows.length - 1] || {}; const pad = { l: 58, r: 18, t: 16, b: 18 }; const barW = cssW - pad.l - pad.r; const rowH = Math.max(30, (cssH - pad.t - pad.b) / series.length); const bars = series.map((s, i) => { const y = pad.t + i * rowH + 6; const v = clamp(Number(row[s.key] || 0), 0, 100); const bw = barW * v / 100; return `${s.label}${Math.round(v)}`; }).join(""); chart.innerHTML = ``; if (ui.colonyChartLegend) ui.colonyChartLegend.innerHTML = series.map(s => `${s.label} ${formatChartValue(s.key, row)}`).join(""); } function chartSeries(mode = "population") { if (mode === "life") return [ { key: "hunger", label: "\u7a7a\u8179", color: "#e4a33f", percent: true }, { key: "lonely", label: "\u5bc2\u3057\u3055", color: "#9b78d4", percent: true }, { key: "stress", label: "\u30b9\u30c8\u30ec\u30b9", color: "#d96262", percent: true }, { key: "mood", label: "\u7dcf\u5408\u7684\u6c17\u5206", color: "#0072b2", percent: true }, ]; if (mode === "objects") return [ { key: "obj_grass", label: "\u8349", color: "#5b9d61" }, { key: "obj_zunchi", label: "\u305a\u3093\u3061", color: "#6f8a3f" }, { key: "obj_trace", label: "\u6b7b\u9ab8", color: "#8a7054" }, ]; if (mode === "environment") return [ { key: "security", label: "\u6cbb\u5b89", color: "#d65e46", percent: true }, { key: "hygiene", label: "\u885b\u751f", color: "#6f8a3f", percent: true }, { key: "happiness", label: "\u5e78\u798f", color: "#d98bc0", percent: true }, ]; return [{ key: "pop", label: "\u500b\u4f53", color: "#5d8ee6" }]; } function formatChartValue(key, values) { const v = values[key] ?? 0; return String(Math.round(v)); }