This commit is contained in:
33333-33333 2026-07-20 14:36:59 +09:00
commit f6d7412744
94 changed files with 3836 additions and 1226 deletions

View file

@ -1,7 +1,5 @@
"use strict";
function escapeHtml(value) { return window.TarinaiUIHelpers.htmlEscape(value); }
function lineageNodeSort(a, b) {
return (a.generation || 1) - (b.generation || 1) ||
(a.birthTime || 0) - (b.birthTime || 0) ||
@ -12,6 +10,10 @@ function lineageNodeSort(a, b) {
function lineageGeneticPercentFromNode(n, key = "") {
const entity = n?.id ? world.tarinai.find(t => (t.familyKey || t.id) === n.id) : null;
if (entity?.geneticStatRatio) return Math.round((entity.geneticStatRatio(key) - 1) * 100);
if (Array.isArray(n?.geneticSummary)) {
const index = { life: 0, size: 1, attack: 2, speed: 3 }[key];
if (Number.isInteger(index) && Number.isFinite(Number(n.geneticSummary[index]))) return Math.round(Number(n.geneticSummary[index]));
}
const g = typeof normalizeGenetics === "function" ? normalizeGenetics(n?.genetics, n?.id || "") : (n?.genetics || {});
const map = { life: "lifeSpanMul", attack: "attackMul", speed: "speedMul", size: "sizeMul" };
const ratio = Number(g?.[map[key] || key]) || 1;
@ -24,11 +26,15 @@ function lineageSignedPercentText(value = 0) {
}
function familyDeathTimeText(value) {
return globalThis.TarinaiMemorialBellSystem?.formatWorldTime?.(value) || "";
}
function lineageTreeNodeHtml(n, x, y) {
const entity = n?.id ? world.tarinai.find(t => (t.familyKey || t.id) === n.id) : null;
const live = entity && !entity.dead ? entity : null;
const img = n ? tarinaiSpritePathForId(n.id) : SPRITES[0].path;
const name = escapeHtml(n?.name || "\u4e0d\u660e");
const name = window.TarinaiUIHelpers.htmlEscape(n?.name || "\u4e0d\u660e");
const gen = n?.generation ? `G${n.generation}` : "G?";
const rawReason = !n?.alive && n?.deathReason ? String(n.deathReason) : "";
const briefReason = familyDeathReasonLabel(rawReason, entity);
@ -38,15 +44,16 @@ function lineageTreeNodeHtml(n, x, y) {
const tags = rawTags.slice();
const displayTags = tags.slice(0, 4);
const tagText = tags.length ? ` / ${tags.join(" ")}` : "";
const title = escapeHtml(`${n?.name || "\u4e0d\u660e"} / \u4e16\u4ee3 ${n?.generation || "?"} / ${status}${reason}${tagText}`);
const deathLine = "";
const tagLine = displayTags.length ? `<span class="family-personality-tags">${displayTags.map(tag => `<em>${escapeHtml(tag)}</em>`).join("")}</span>` : "";
const title = window.TarinaiUIHelpers.htmlEscape(`${n?.name || "\u4e0d\u660e"} / \u4e16\u4ee3 ${n?.generation || "?"} / ${status}${reason}${tagText}`);
const deathTimeText = !n?.alive ? familyDeathTimeText(n?.deathTime) : "";
const deathLine = deathTimeText ? `<span class="family-death-time">${window.TarinaiUIHelpers.htmlEscape(deathTimeText)}</span>` : "";
const tagLine = displayTags.length ? `<span class="family-personality-tags">${displayTags.map(tag => `<em>${window.TarinaiUIHelpers.htmlEscape(tag)}</em>`).join("")}</span>` : "";
const champion = !!(live?.isTarinaiChampion || n?.isTarinaiChampion);
const statLine = n ? `<span class="family-genetic-stats" title="\u5BFF\u547D / \u4F53\u683C / \u653B\u6483 / \u901F\u5EA6">
<span><b>\u5BFF</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "life")))}</span>
<span><b>\u4F53</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "size")))}</span>
<span><b>\u653B</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "attack")))}</span>
<span><b>\u901F</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "speed")))}</span>
<span><b>\u5BFF</b>${window.TarinaiUIHelpers.htmlEscape(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "life")))}</span>
<span><b>\u4F53</b>${window.TarinaiUIHelpers.htmlEscape(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "size")))}</span>
<span><b>\u653B</b>${window.TarinaiUIHelpers.htmlEscape(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "attack")))}</span>
<span><b>\u901F</b>${window.TarinaiUIHelpers.htmlEscape(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "speed")))}</span>
</span>` : "";
const cls = n ? ((live || n.alive) ? "alive" : "dead") : "missing";
const childCls = (n?.parents || []).length ? " child" : "";
@ -54,12 +61,12 @@ function lineageTreeNodeHtml(n, x, y) {
? (live.effectiveScale ? live.effectiveScale() : (Number.isFinite(live.scale) ? live.scale : (Number.isFinite(live.adultScale) ? live.adultScale : 0.28)))
: (Number.isFinite(n?.scale) ? n.scale : (Number.isFinite(n?.adultScale) ? n.adultScale : 0.28));
const iconScale = clamp((Number(iconBaseScale) || 0.28) / 0.28, 0.64, 1.42).toFixed(2);
const data = n?.id ? ` data-family-tarinai-id="${escapeHtml(n.id)}"` : "";
const data = n?.id ? ` data-family-tarinai-id="${window.TarinaiUIHelpers.htmlEscape(n.id)}"` : "";
return `<div class="family-node tree-node ${cls}${childCls}${champion ? " champion" : ""}"${data} title="${title}" style="left:${Math.round(x)}px;top:${Math.round(y)}px;--family-icon-scale:${iconScale}">
<div class="family-portrait" aria-hidden="true"><img src="${img}" loading="lazy" decoding="async" alt=""></div>
<div class="family-copy">
<strong>${champion ? "\u265B " : ""}${name}</strong>
<span>${escapeHtml(gen)} / ${escapeHtml(status)}${champion ? " / \u738B\u8005" : ""}</span>
<span>${window.TarinaiUIHelpers.htmlEscape(gen)} / ${window.TarinaiUIHelpers.htmlEscape(status)}${champion ? " / \u738B\u8005" : ""}</span>
${statLine}
${deathLine}
${tagLine}
@ -378,7 +385,7 @@ function lineageBuildRowGroups(row, idSet) {
function lineageLayoutRows(rows, idSet) {
const NODE_W = 150;
const NODE_H = 104;
const NODE_H = 112;
const SIBLING_GAP = 24;
const GROUP_GAP = 42;
const PARTNER_GROUP_GAP = 18;