"use strict";
function escapeHtml(value) { return window.TarinaiUIHelpers.htmlEscape(value); }
function quoteNameInRecordText(text, name) {
const raw = String(text || "");
const n = String(name || "").trim();
if (!n) return raw;
const parts = raw.split(n);
if (parts.length <= 1) return raw;
let out = "";
for (let i = 0; i < parts.length - 1; i++) {
const before = parts[i].slice(-1);
const after = parts[i + 1].charAt(0);
out += parts[i] + ((before === "\u300c" && after === "\u300d") ? n : `\u300c${n}\u300d`);
}
return out + parts[parts.length - 1];
}
function renameSelectedTarinai(rawName) {
const t = world.selected;
if (!t || t.dead || !world.tarinai.includes(t)) return false;
const next = String(rawName || "").replace(/\s+/g, " ").trim().slice(0, 18);
if (!next) {
uiCache.selectedSnapshot = "";
renderSelected();
return false;
}
if (next === t.name) return false;
const oldName = t.name;
t.name = next;
if (t.isZunchiSlave) t.formerName = next;
world.recordFamily?.(t);
world.markFamilyTreeDirty?.("rename");
uiCache.archiveFamilyVersion = null;
uiCache.selectedSnapshot = "";
renderSelected();
renderArchive?.();
render?.();
showToast(`\u300c${oldName}\u300d\u3092\u300c${next}\u300d\u306b\u6539\u540d\u3057\u307e\u3057\u305f\u3002`);
return true;
}
function personalityValueText(value) {
const n = clamp(Number(value) || 0, -1, 1);
return `${n >= 0 ? "+" : ""}${n.toFixed(2)}`;
}
function personalityRowsHtml(personality = {}) {
const p = normalizePersonality(personality);
return PERSONALITY_KEYS.map(key => `${escapeHtml(PERSONALITY_LABELS[key]?.label || key)}: ${personalityValueText(p[key])}`).join("");
}
function personalityShortLabel(key) {
return ({
aggression: "\u653b\u6483",
openness: "\u958b\u653e",
sociability: "\u793e\u4ea4",
neuroticism: "\u795e\u7d4c",
})[key] || (PERSONALITY_LABELS[key]?.label || key);
}
function personalityDisplayCauseText(cause = "") {
return String(cause || "").replace(/(?:\u306e)?\u5f71\u97ff\u3067$/g, "").trim();
}
function geneticPercentText(t, key = "") {
const ratio = t?.geneticStatRatio ? t.geneticStatRatio(key) : 1;
const pct = Math.round((ratio - 1) * 100);
return `${pct >= 0 ? "+" : ""}${pct}%`;
}
function geneticSizeText(t) {
const adult = Number.isFinite(t?.adultScale) ? t.adultScale : (t?.scale || 0.28);
const now = Number.isFinite(t?.scale) ? t.scale : adult;
const growth = Math.round((t?.growth ?? 1) * 100);
return `${geneticPercentText(t, "size")} / ${now.toFixed(2)}\u2192${adult.toFixed(2)} (${growth}%)`;
}
function geneticComfortTemperatureText(t) {
const raw = t?.genetics?.temperatureOffset;
const offset = Number.isFinite(Number(raw)) ? Number(raw) : 0;
const min = (CONFIG.temperatureComfortMin ?? 10) + offset;
const max = (CONFIG.temperatureComfortMax ?? 25) + offset;
const sign = offset >= 0 ? "+" : "";
return `${min.toFixed(1)}\u301C${max.toFixed(1)}\u2103 (${sign}${offset.toFixed(1)}\u2103)`;
}
function personalityDailyChangeHtml(t) {
const state = typeof personalityDailyState === "function" ? personalityDailyState(t) : (t?.personalityDaily || { day: world?.day || 1, byKey: {}, byCause: {} });
const dayLabel = `D${state.day || world?.day || 1}`;
const causeMap = new Map();
const addCauseChange = (cause, key, up, down) => {
const cleanCause = personalityDisplayCauseText(cause || "") || "\u8981\u56e0\u4e0d\u660e";
if (!causeMap.has(cleanCause)) causeMap.set(cleanCause, new Map());
const byKey = causeMap.get(cleanCause);
const current = byKey.get(key) || { up: 0, down: 0 };
current.up += Math.max(0, Number(up) || 0);
current.down += Math.max(0, Number(down) || 0);
byKey.set(key, current);
};
for (const [cause, entry] of Object.entries(state.byCause || {})) {
for (const key of PERSONALITY_KEYS) {
const v = entry?.byKey?.[key] || {};
const up = Math.max(0, Number(v.up) || 0);
const down = Math.max(0, Number(v.down) || 0);
if (up > 0.0001 || down > 0.0001) addCauseChange(cause, key, up, down);
}
}
if (!causeMap.size) {
for (const key of PERSONALITY_KEYS) {
const entry = state.byKey?.[key] || {};
const up = Math.max(0, Number(entry.up) || 0);
const down = Math.max(0, Number(entry.down) || 0);
if (up <= 0.0001 && down <= 0.0001) continue;
const causes = Array.isArray(entry.causes) ? [...new Set(entry.causes.map(c => personalityDisplayCauseText(c)).filter(Boolean))].slice(0, 4) : [];
for (const cause of causes.length ? causes : ["\u8981\u56e0\u4e0d\u660e"]) addCauseChange(cause, key, up, down);
}
}
const rows = [];
for (const [cause, byKey] of causeMap.entries()) {
const parts = [];
for (const key of PERSONALITY_KEYS) {
const entry = byKey.get(key);
if (!entry) continue;
const up = Math.max(0, Number(entry.up) || 0);
const down = Math.max(0, Number(entry.down) || 0);
if (up > 0.0001) parts.push(`${personalityShortLabel(key)} +${up.toFixed(3)}`);
if (down > 0.0001) parts.push(`${personalityShortLabel(key)} -${down.toFixed(3)}`);
}
if (parts.length) rows.push(`
${escapeHtml(cause)}${escapeHtml(parts.join(" / "))}${escapeHtml(dayLabel)}`);
}
return rows.join("") || `\u8981\u56e0\u5909\u5316\u306a\u3057${escapeHtml(dayLabel)}`;
}
function personalityDailySnapshot(t) {
const state = typeof personalityDailyState === "function" ? personalityDailyState(t) : (t?.personalityDaily || {});
const keyPart = PERSONALITY_KEYS.map(key => {
const entry = state.byKey?.[key] || {};
const causes = Array.isArray(entry.causes) ? entry.causes.join("/") : "";
return `${key}:${Number(entry.up || 0).toFixed(4)}:${Number(entry.down || 0).toFixed(4)}:${causes}`;
}).join(",");
const causePart = Object.entries(state.byCause || {}).map(([cause, entry]) => {
const values = PERSONALITY_KEYS.map(key => {
const v = entry?.byKey?.[key] || {};
return `${key}:${Number(v.up || 0).toFixed(4)}:${Number(v.down || 0).toFixed(4)}`;
}).join(";");
return `${cause}=${values}`;
}).join("|");
return `${keyPart}#${causePart}`;
}
function selectedDataCategoryHtml(id, label, bodyHtml) {
if (!uiCache.selectedCollapsedCategories) uiCache.selectedCollapsedCategories = new Set();
const collapsed = uiCache.selectedCollapsedCategories.has(id);
return `
${bodyHtml}
`;
}
function selectedInfoRowHtml(label, valueHtml, metric = "") {
const metricAttr = metric ? ` data-metric="${escapeHtml(metric)}"` : "";
return `${escapeHtml(label)}${valueHtml}
`;
}
function selectedInventoryItems(t) {
const worldRef = t?.world || world;
const labels = { grass_bed: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9", plushie: "\u306c\u3044\u3050\u308b\u307f" };
const items = (worldRef?.ownedItemsFor?.(t?.id) || worldRef?.items || []).filter(it => it && !it.dead && it.isStructure && it.ownerId === t?.id);
const counts = new Map();
for (const it of items) {
const label = labels[it.type] || toolLabel(it.type) || it.type;
counts.set(label, (counts.get(label) || 0) + 1);
}
return Array.from(counts.entries()).map(([label, count]) => count > 1 ? `${label} \u00d7${count}` : label);
}
function selectedInventoryHtml(t) {
const entries = selectedInventoryItems(t);
if (!entries.length) return `\u6240\u6301\u54c1\u306a\u3057
`;
return `\u6240\u6301\u54c1${entries.map(v => `${escapeHtml(v)}`).join("")}
`;
}
function renderSelected() {
const t = world.selected;
if (!t || t.dead || !world.tarinai.includes(t)) {
const showEmpty = Boolean(uiCache.showSelectedEmpty);
ui.selectedCard?.classList.toggle("hidden", !showEmpty);
if (uiCache.selectedEmpty && uiCache.selectedEmptyVisible === showEmpty) return;
uiCache.selectedSnapshot = "";
uiCache.selectedEmpty = true;
uiCache.selectedEmptyVisible = showEmpty;
ui.selectedInfo.className = "selected-info empty";
ui.selectedInfo.textContent = "\u305f\u308a\u306a\u3044\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044";
return;
}
uiCache.showSelectedEmpty = false;
ui.selectedCard?.classList.remove("hidden");
uiCache.selectedEmpty = false;
uiCache.selectedEmptyVisible = false;
const activeNameInput = document.activeElement?.matches?.("[data-selected-name]") && document.activeElement?.dataset?.selectedId === t.id;
if (activeNameInput) return;
const rel = t.relationSummary ? t.relationSummary() : {};
const generationDisplay = t.displayGeneration ? t.displayGeneration() : ((t.hasPaired || t.parents?.length || t.children?.length) ? t.generation : "");
ensurePersonality(t);
const personalityTags = t.personalityTags ? t.personalityTags() : getPersonalityTraitTags(t);
const personalitySnapshot = [
...PERSONALITY_KEYS.map(key => personalityValueText(t.birthPersonality?.[key])),
...PERSONALITY_KEYS.map(key => personalityValueText(t.currentPersonality?.[key])),
personalityTags.join(",")
].join(",");
const itemEffectSummary = t.activeItemEffectSummary ? t.activeItemEffectSummary() : [];
const needKeys = typeof TARINAI_NEED_KEYS !== "undefined" ? TARINAI_NEED_KEYS : ["food", "sleep", "health", "safety", "social", "fulfill"];
const behaviorState = currentTarinaiBehavior(t);
const snapshot = [
t.id, world?.groundType || "soil", t.type, t.name, generationDisplay, t.state, t.thought, targetLabel(t.target), fmt(Math.max(0, Number(t.electricShockUntil || 0) - Number(world?.time || 0))),
behaviorState?.need || "", behaviorState?.actionId || "", behaviorState?.label || "", behaviorState?.reason || "", behaviorState?.source || "", behaviorState?.text || "", needKeys.map(key => `${key}:${t.needs?.[key] ?? 0}`).join(","),
fmt(t.age), fmt(t.lifeSpan), fmt(t.hunger), fmt(t.loneliness),
fmt(t.energy), fmt(t.stress), fmt(t.mood),
t.parentNames?.join("+") || "", t.children?.length || 0, fmt(t.lack),
relationEntries(t, "friend").map(r => `${r.id}:${fmt(r.score)}:${r.name}`).join(","), relationEntries(t, "enemy").map(r => `${r.id}:${fmt(r.score)}:${r.name}`).join(","), fightWinRateLabel(t),
t.isZunchiSlave ? 1 : 0, t.isTarinaiChampion ? 1 : 0, fmt(t.totalFightWins || 0), fmt(t.totalFightLosses || 0), fmt(t.loveMochiTimer || 0), fmt(t.fightMochiTimer || 0),
t.zunchiDisease ? 1 : 0, fmt(t.zunchiDiseaseSeverity || 0), fmt(t.zunchiStain || 0),
t.sleepDisease ? 1 : 0, t.explosionDisease ? 1 : 0, fmt(t.explosionDiseaseTimer || 0), t.fightDisease ? 1 : 0, t.favorite ? 1 : 0,
personalitySnapshot, personalityDailySnapshot(t),
["life", "attack", "speed", "size"].map(key => geneticPercentText(t, key)).join(","), geneticComfortTemperatureText(t), fmt((t.growth || 0) * 100),
itemEffectSummary.map(e => `${e.id}:${e.detail}`).join(","),
selectedInventoryItems(t).join(","),
(t.records || []).slice(0, 7).map(r => `${Math.round(r.time || 0)}:${r.kind || ""}:${r.text || ""}`).join("~")
].join("|");
if (uiCache.selectedSnapshot === snapshot) return;
uiCache.selectedSnapshot = snapshot;
ui.selectedInfo.className = "selected-info";
const effectBadges = [];
if (t.isZunchiSlave) effectBadges.push(`\u305a\u3093\u3061\u3069\u308c\u3044`);
if (t.isTarinaiChampion) effectBadges.push(`\u265b \u305f\u308a\u306a\u3044\u738b\u8005`);
if (t.favorite) effectBadges.push(`\u2605`);
if (t.zunchiDisease) effectBadges.push(`\u305a\u3093\u3061\u75c5 ${fmt((t.zunchiDiseaseSeverity || 0) * 100)}`);
if (t.sleepDisease) effectBadges.push(`\u306d\u3080\u308a\u75c5`);
if (t.explosionDisease) effectBadges.push(`\u7206\u767a\u75c5 ${fmt(t.explosionDiseaseTimer || 0)}`);
if (t.fightDisease) effectBadges.push(`\u304d\u305a\u3064\u304d\u75c5`);
if ((t.loveMochiTimer || 0) > 0.1) effectBadges.push(`\u3078\u3053\u9905 ${fmt(t.loveMochiTimer)}`);
if ((t.fightMochiTimer || 0) > 0.1) effectBadges.push(`\u3051\u3093\u304b\u9905 ${fmt(t.fightMochiTimer)}`);
const personalityTagHtml = personalityTags.length
? personalityTags.map(tag => `${escapeHtml(tag)}`).join("")
: `\u4e2d\u7acb`;
const recentChangeHtml = personalityDailyChangeHtml(t);
const personalityHtml = `
\u6027\u683c\u30bf\u30b0${personalityTagHtml}
\u8a95\u751f\u6642\u6027\u683c${personalityRowsHtml(t.birthPersonality)}
\u73fe\u5728\u6027\u683c${personalityRowsHtml(t.currentPersonality)}
\u4eca\u65e5\u306e\u5909\u5316${recentChangeHtml}
`;
const recentMemoryHtml = (t.records || []).slice(0, 7).map(r => {
const timeText = world.clockString ? (world.clockStringFromTime ? world.clockStringFromTime(r.time || 0) : formatRecordTime(r.time || 0)) : formatRecordTime(r.time || 0);
const recordText = quoteNameInRecordText(r.text || "", t.name);
return `${escapeHtml(timeText)}${escapeHtml(recordText)}`;
}).join("") || `-\u307e\u3060\u8a18\u61b6\u304c\u3042\u308a\u307e\u305b\u3093`;
const basicHtml = `
\u4e16\u4ee3${escapeHtml(generationDisplay || "\u4e0d\u660e")}
\u5e74\u9f62${fmt(t.age)} / ${fmt(t.lifeSpan)}
\u6700\u5927\u5bff\u547d${geneticPercentText(t, "life")}
\u653b\u6483\u529b${geneticPercentText(t, "attack")}
\u79fb\u52d5\u901f\u5ea6${geneticPercentText(t, "speed")}
\u30b5\u30a4\u30ba${geneticSizeText(t)}
\u5FEB\u9069\u6E29\u5EA6${geneticComfortTemperatureText(t)}
`;
const needRowsHtml = needKeys.map(key => {
const label = (typeof TARINAI_NEED_LABELS !== "undefined" && TARINAI_NEED_LABELS[key]) || key;
const value = typeof getNeedDisplayValue === "function" ? getNeedDisplayValue(t.needs?.[key] || 0) : Math.round((t.needs?.[key] || 0) / 10);
const chosen = behaviorState?.need === key ? ` data-chosen-need="true"` : "";
return `${escapeHtml(label)}${escapeHtml(value)}
`;
}).join("");
const healthHtml = [
`\u884c\u52d5${escapeHtml(reasonLabel(t))}
`,
`${needRowsHtml}
`,
selectedInfoRowHtml("\u4f53\u529b", `${fmt(t.energy)} / ${fmt(typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100))}`, "energy"),
selectedInfoRowHtml("\u30b9\u30c8\u30ec\u30b9", fmt(t.stress), "stress"),
selectedInfoRowHtml("\u4F53\u611F\u6C17\u6E29", `${(world.feltTemperatureFor?.(t) ?? world.temperatureAt?.(t.x, t.y, t) ?? t.feltTemperature ?? CONFIG.standardTemperature ?? 15).toFixed(1)}\u2103`, "temperature"),
selectedInfoRowHtml("\u6e80\u8179\u5ea6", fmt(Math.max(0, Math.min(100, 100 - (Number(t.hunger) || 0)))), "fullness"),
].join("");
const itemEffectHtml = itemEffectSummary.length
? itemEffectSummary.map(e => `${escapeHtml(e.label)}${escapeHtml(e.detail)}
`).join("")
: `\u30a2\u30a4\u30c6\u30e0\u52b9\u679c\u306a\u3057
`;
const relationHtml = `
\u53cb\u9054${relationListHtml(t, "friend")}
\u6575\u5bfe${relationListHtml(t, "enemy")}
\u89aa${escapeHtml(t.parentNames?.length ? t.parentNames.join(" + ") : "\u4e0d\u660e")}
\u5b50${t.children?.length || 0}
\u3051\u3093\u304b\u52dd\u7387${escapeHtml(fightWinRateLabel(t))}
\u79f0\u53f7${t.isTarinaiChampion ? "\u305f\u308a\u306a\u3044\u738b\u8005" : "\u306a\u3057"}
`;
ui.selectedInfo.innerHTML = `
${effectBadges.length ? `${effectBadges.join("")}
` : ""}
${selectedDataCategoryHtml("basic", "\u57fa\u672c", basicHtml)}
${selectedDataCategoryHtml("health", "\u6b32\u6c42", healthHtml)}
${selectedDataCategoryHtml("effects", "\u30a2\u30a4\u30c6\u30e0\u52b9\u679c", itemEffectHtml)}
${selectedDataCategoryHtml("personality", "\u6027\u683c", personalityHtml)}
${selectedDataCategoryHtml("relation", "\u95a2\u4fc2\u3068\u5bb6\u65cf", relationHtml)}
${selectedDataCategoryHtml("inventory", "\u6240\u6301\u54c1", selectedInventoryHtml(t))}
${selectedDataCategoryHtml("memories", "\u6700\u8fd1\u306e\u8a18\u61b6", `
`)}
`;
}
function formatRecordTime(time) {
const totalMinutes = Math.floor(((time || 0) % (CONFIG.dayLength || 120)) / (CONFIG.dayLength || 120) * 24 * 60);
const hh = String(Math.floor(totalMinutes / 60)).padStart(2, "0");
const mm = String(totalMinutes % 60).padStart(2, "0");
return `${hh}:${mm}`;
}
function stateLabel(s) {
return window.TEXT_CATALOG?.stateLabel?.(s) || s || "";
}
function groundAwareIdleText(t) {
return window.TEXT_CATALOG?.idleGroundText?.(t) || "\u307c\u30fc\u3063\u3068\u3057\u3066\u3044\u308b\u3002";
}
function isIdleDisplayText(value = "") {
return ["\u5f85\u6a5f", "\u5f85\u3063\u3066\u3044\u308b", "\u5f85\u3063\u3066\u3044\u308b\u3002", "\u307c\u30fc\u3063\u3068\u3057\u3066\u3044\u308b", "\u307c\u30fc\u3063\u3068\u3057\u3066\u3044\u308b\u3002", "\u5468\u56f2\u3092\u898b\u3066\u3044\u308b"].includes(String(value || "").trim());
}
function reasonLabel(t) {
if (!t) return "\u306a\u3057";
if (Number(t.electricShockUntil || 0) > Number(t.world?.time || world?.time || 0)) return "\u611f\u96fb\u3057\u3066\u3044\u308b\u3002";
if (t.state === "play_seesaw" && globalThis.TarinaiSeesawSystem?.isPlaying?.(t, t.world || world)) return "\u30b7\u30fc\u30bd\u30fc\u3092\u3057\u3066\u3044\u308b\u3002";
const behavior = behaviorText(t);
if (behavior) {
const normalized = window.TEXT_CATALOG?.cleanBehaviorText?.(behavior, t.state || "") || behavior;
if (isIdleDisplayText(normalized)) return window.TEXT_CATALOG?.temperatureEndureText?.(t) || groundAwareIdleText(t);
return normalized;
}
const state = String(t.state || "");
const live = window.TEXT_CATALOG?.reasonLabel?.(t) || "";
const liveStates = new Set(["birth_ritual", "eat", "sleep", "panic", "fight", "intimidate", "ant_attack", "ant_intimidate", "frozen"]);
if (live && (liveStates.has(state) || (t.birthRitualTimer || 0) > 0.04 || (t.eatTimer || 0) > 0.04 || (t.fightTimer || 0) > 0.04 || (t.intimidateTimer || 0) > 0.04)) return live;
const behaviorState = currentTarinaiBehavior(t);
if (behaviorState?.reason || behaviorState?.text) {
const raw = behaviorState.reason || behaviorState.text;
const normalized = window.TEXT_CATALOG?.cleanBehaviorText?.(raw, behaviorState?.actionId || t.state || "") || raw;
if (isIdleDisplayText(normalized)) return window.TEXT_CATALOG?.temperatureEndureText?.(t) || groundAwareIdleText(t);
return normalized;
}
if (isIdleDisplayText(live)) return window.TEXT_CATALOG?.temperatureEndureText?.(t) || groundAwareIdleText(t);
if ((!live || live === "\u306a\u3057") && (String(t.state || "") === "idle" || !t.state)) return window.TEXT_CATALOG?.temperatureEndureText?.(t) || groundAwareIdleText(t);
return live || "\u306a\u3057";
}
function targetLabel(target) {
return window.TEXT_CATALOG?.targetLabel?.(target) || "\u306a\u3057";
}
function fightWinRateLabel(t) {
const won = Number(t.totalFightWins || 0);
const lost = Number(t.totalFightLosses || 0);
const total = won + lost;
if (!total) return "\u306a\u3057";
return `${Math.round((won / total) * 100)}% (${won}\u52dd${lost}\u6557)`;
}
function setTextIfChanged(el, key, value) {
if (!el) return;
const next = String(value);
if (uiCache.stats[key] === next) return;
uiCache.stats[key] = next;
el.textContent = next;
}
function escapeRegExp(value) {
return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function formatLogDisplayText(text) {
let out = String(text ?? "");
const names = new Set();
for (const t of world?.tarinai || []) if (t?.name) names.add(t.name);
for (const n of Object.values(world?.family || {})) if (n?.name) names.add(n.name);
const sorted = [...names].filter(Boolean).sort((a, b) => b.length - a.length);
for (const name of sorted) {
const re = new RegExp(`(^|[^\u300c])(${escapeRegExp(name)})(?=[\u3068\u306f\u304c\u3092\u306b\u306e\u3082\u3078\u304b\u3089\u3067\u3001\u3002]|$)`, "g");
out = out.replace(re, (m, prefix, found) => `${prefix}\u300c${found}\u300d`);
}
return out;
}