This commit is contained in:
33333-33333 2026-06-28 13:40:41 +09:00
commit f500279abd
93 changed files with 1893 additions and 1198 deletions

View file

@ -7,6 +7,29 @@ function lineageNodeSort(a, b) {
String(a.id || "").localeCompare(String(b.id || ""));
}
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);
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;
return Math.round((ratio - 1) * 100);
}
function lineageSignedPercentText(value = 0) {
const n = Math.round(Number(value) || 0);
return `${n >= 0 ? "+" : ""}${n}%`;
}
function lineageFightRateText(n) {
const entity = n?.id ? world.tarinai.find(t => (t.familyKey || t.id) === n.id) : null;
const wins = Number(entity?.totalFightWins ?? n?.totalFightWins ?? 0) || 0;
const losses = Number(entity?.totalFightLosses ?? n?.totalFightLosses ?? 0) || 0;
const total = wins + losses;
if (!total) return "--";
return `${Math.round((wins / total) * 100)}%/${total}`;
}
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;
@ -23,6 +46,8 @@ function lineageTreeNodeHtml(n, x, y) {
const title = escapeHtml(`${n?.name || "\u4e0d\u660e"} / \u4e16\u4ee3 ${n?.generation || "?"} / ${status}${reason}${tagText}`);
const deathLine = briefReason ? `<span class="death-reason">${escapeHtml(briefReason)}</span>` : "";
const tagLine = tags.length ? `<span class="family-personality-tags">${tags.map(tag => `<em>${escapeHtml(tag)}</em>`).join("")}</span>` : "";
const champion = !!(live?.isTarinaiChampion || n?.isTarinaiChampion);
const statLine = n ? `<span class="family-genetic-stats" title="寿命 / 体格 / 攻撃 / 速度 / 勝率"><b>寿</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "life")))} <b>体</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "size")))} <b>攻</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "attack")))} <b>速</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "speed")))} <b>勝</b>${escapeHtml(lineageFightRateText(n))}</span>` : "";
const cls = n ? ((live || n.alive) ? "alive" : "dead") : "missing";
const childCls = (n?.parents || []).length ? " child" : "";
const iconBaseScale = live
@ -30,11 +55,12 @@ function lineageTreeNodeHtml(n, x, y) {
: (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)}"` : "";
return `<div class="family-node tree-node ${cls}${childCls}"${data} title="${title}" style="left:${Math.round(x)}px;top:${Math.round(y)}px;--family-icon-scale:${iconScale}">
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>${name}</strong>
<span>${escapeHtml(gen)} / ${escapeHtml(status)}</span>
<strong>${champion ? "♛ " : ""}${name}</strong>
<span>${escapeHtml(gen)} / ${escapeHtml(status)}${champion ? " / 王者" : ""}</span>
${statLine}
${deathLine}
${tagLine}
</div>
@ -351,8 +377,8 @@ function lineageBuildRowGroups(row, idSet) {
}
function lineageLayoutRows(rows, idSet) {
const NODE_W = 132;
const NODE_H = 84;
const NODE_W = 150;
const NODE_H = 104;
const SIBLING_GAP = 32;
const GROUP_GAP = 52;
const PARTNER_GROUP_GAP = 24;