gistfile1.txt
· 5.4 KiB · Text
Eredeti
function formatRow(
row: ResultRow,
context: NationContext,
allNames: string[],
allScores: string[],
allKds: string[],
allAtks: string[],
allDefs: string[]
): string {
const char = row.character;
const goal = Config.get({ section: "wrank", key: "goal" });
const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1);
const classKey = (typeof char.class === "object" ? char.class?.key : char.class) as ClassKey;
const scoreEmoji = Emoji.get("score") || "📊";
const scoreText = row.score ? format.scoreBold(row.score.pts) : "—";
const kdText = row.score && (row.score.k || row.score.d) ? format.kd(row.score.k ?? 0, row.score.d ?? 0) : "—";
// Column targets combine primary + secondary stat widths, so the main
// row makes room for the stats line beneath it.
const scoreColumn = [...allScores, ...allAtks];
const kdColumn = [...allKds, ...allDefs];
const tokens: Record<string, string> = {
wrank: Layout.wrank(wrEntry, goal, context),
class: Emoji.class(classKey) || classKey || "?",
name: TextAlign.padToMax(char.name, allNames),
indicators: Layout.indicators(char as any, { historyKey: row.historyKey }),
score: `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`,
kd: TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn),
};
const mainLine = Layout.formatRow(TEMPLATE, tokens);
const hasStats = row.score && (row.score.atk || row.score.def || row.score.heal);
log.debug(`formatRow stats check: char=${char.name} score=${JSON.stringify(row.score)} hasStats=${hasStats}`);
if (!hasStats) return mainLine;
const prefixText = `${tokens.wrank} ${tokens.class} ${tokens.name}${tokens.indicators}`;
const prefixGap = TextAlign.padLeft("", TextAlign.estimateWidth(prefixText));
const atkText = format.statText("anima_atk", row.score!.atk, "⚔️");
const defText = format.statText("anima_def", row.score!.def, "🛡️");
const healText = format.statText("circle_massheal_purple", row.score!.heal, "💚");
const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMaxOffset(defText, kdColumn, DEF_WIDTH_OFFSET)} ${TextAlign.gap(HEAL_GAP)}${healText}`;
return `${mainLine}\n${statsLine}`;
}
function buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder {
const { date, slot } = TGKey.parse(historyKey);
const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella);
const procyonRows = rows.filter((r) => r.character.nation === Nation.Procyon);
const capContext = Layout.nationContext(capellaRows);
const proContext = Layout.nationContext(procyonRows);
const sortByScore = (a: ResultRow, b: ResultRow) => (b.score?.pts ?? 0) - (a.score?.pts ?? 0);
const sortedCapella = [...capellaRows].sort(sortByScore);
const sortedProcyon = [...procyonRows].sort(sortByScore);
const capellaNames = sortedCapella.map((r) => r.character.name);
const procyonNames = sortedProcyon.map((r) => r.character.name);
const capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
const procyonScores = sortedProcyon.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
const capellaKds = sortedCapella.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—");
const procyonKds = sortedProcyon.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—");
const atkEmoji = Emoji.get("atk") || "⚔️";
const defEmoji = Emoji.get("def") || "🛡️";
const capellaAtks = sortedCapella.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : "");
const procyonAtks = sortedProcyon.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : "");
const capellaDefs = sortedCapella.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : "");
const procyonDefs = sortedProcyon.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : "");
const capK = capellaRows.reduce((s, r) => s + (r.score?.k ?? 0), 0);
const capD = capellaRows.reduce((s, r) => s + (r.score?.d ?? 0), 0);
const proK = procyonRows.reduce((s, r) => s + (r.score?.k ?? 0), 0);
const proD = procyonRows.reduce((s, r) => s + (r.score?.d ?? 0), 0);
const capellaEmoji = Emoji.get("capella");
const procyonEmoji = Emoji.get("procyon");
const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNames, capellaScores, capellaKds, capellaAtks, capellaDefs));
const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNames, procyonScores, procyonKds, procyonAtks, procyonDefs));
const embed = new EmbedBuilder()
.setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)
.setColor(0xe8a317)
.setFooter({ text: `TG Result · ${TGKey.toDisplay(historyKey)}` })
.setTimestamp();
EmbedHelpers.addPerPlayerColumn(embed, `${capellaEmoji} Capella${(capK || capD) ? ` — ${format.kd(capK, capD)}` : ""}`, capellaFormatted);
embed.addFields({ name: "\u200b", value: "\u200b", inline: false });
EmbedHelpers.addPerPlayerColumn(embed, `${procyonEmoji} Procyon${(proK || proD) ? ` — ${format.kd(proK, proD)}` : ""}`, procyonFormatted);
return embed;
}
| 1 | function formatRow( |
| 2 | row: ResultRow, |
| 3 | context: NationContext, |
| 4 | allNames: string[], |
| 5 | allScores: string[], |
| 6 | allKds: string[], |
| 7 | allAtks: string[], |
| 8 | allDefs: string[] |
| 9 | ): string { |
| 10 | const char = row.character; |
| 11 | const goal = Config.get({ section: "wrank", key: "goal" }); |
| 12 | const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1); |
| 13 | const classKey = (typeof char.class === "object" ? char.class?.key : char.class) as ClassKey; |
| 14 | |
| 15 | const scoreEmoji = Emoji.get("score") || "📊"; |
| 16 | const scoreText = row.score ? format.scoreBold(row.score.pts) : "—"; |
| 17 | const kdText = row.score && (row.score.k || row.score.d) ? format.kd(row.score.k ?? 0, row.score.d ?? 0) : "—"; |
| 18 | |
| 19 | // Column targets combine primary + secondary stat widths, so the main |
| 20 | // row makes room for the stats line beneath it. |
| 21 | const scoreColumn = [...allScores, ...allAtks]; |
| 22 | const kdColumn = [...allKds, ...allDefs]; |
| 23 | |
| 24 | const tokens: Record<string, string> = { |
| 25 | wrank: Layout.wrank(wrEntry, goal, context), |
| 26 | class: Emoji.class(classKey) || classKey || "?", |
| 27 | name: TextAlign.padToMax(char.name, allNames), |
| 28 | indicators: Layout.indicators(char as any, { historyKey: row.historyKey }), |
| 29 | score: `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`, |
| 30 | kd: TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn), |
| 31 | }; |
| 32 | |
| 33 | const mainLine = Layout.formatRow(TEMPLATE, tokens); |
| 34 | |
| 35 | const hasStats = row.score && (row.score.atk || row.score.def || row.score.heal); |
| 36 | log.debug(`formatRow stats check: char=${char.name} score=${JSON.stringify(row.score)} hasStats=${hasStats}`); |
| 37 | if (!hasStats) return mainLine; |
| 38 | |
| 39 | const prefixText = `${tokens.wrank} ${tokens.class} ${tokens.name}${tokens.indicators}`; |
| 40 | const prefixGap = TextAlign.padLeft("", TextAlign.estimateWidth(prefixText)); |
| 41 | const atkText = format.statText("anima_atk", row.score!.atk, "⚔️"); |
| 42 | const defText = format.statText("anima_def", row.score!.def, "🛡️"); |
| 43 | const healText = format.statText("circle_massheal_purple", row.score!.heal, "💚"); |
| 44 | |
| 45 | const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMaxOffset(defText, kdColumn, DEF_WIDTH_OFFSET)} ${TextAlign.gap(HEAL_GAP)}${healText}`; |
| 46 | |
| 47 | return `${mainLine}\n${statsLine}`; |
| 48 | } |
| 49 | |
| 50 | function buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder { |
| 51 | const { date, slot } = TGKey.parse(historyKey); |
| 52 | |
| 53 | const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella); |
| 54 | const procyonRows = rows.filter((r) => r.character.nation === Nation.Procyon); |
| 55 | |
| 56 | const capContext = Layout.nationContext(capellaRows); |
| 57 | const proContext = Layout.nationContext(procyonRows); |
| 58 | |
| 59 | const sortByScore = (a: ResultRow, b: ResultRow) => (b.score?.pts ?? 0) - (a.score?.pts ?? 0); |
| 60 | |
| 61 | const sortedCapella = [...capellaRows].sort(sortByScore); |
| 62 | const sortedProcyon = [...procyonRows].sort(sortByScore); |
| 63 | |
| 64 | const capellaNames = sortedCapella.map((r) => r.character.name); |
| 65 | const procyonNames = sortedProcyon.map((r) => r.character.name); |
| 66 | const capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—"); |
| 67 | const procyonScores = sortedProcyon.map((r) => r.score ? format.scoreBold(r.score.pts) : "—"); |
| 68 | const capellaKds = sortedCapella.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—"); |
| 69 | const procyonKds = sortedProcyon.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—"); |
| 70 | |
| 71 | const atkEmoji = Emoji.get("atk") || "⚔️"; |
| 72 | const defEmoji = Emoji.get("def") || "🛡️"; |
| 73 | const capellaAtks = sortedCapella.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : ""); |
| 74 | const procyonAtks = sortedProcyon.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : ""); |
| 75 | const capellaDefs = sortedCapella.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : ""); |
| 76 | const procyonDefs = sortedProcyon.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : ""); |
| 77 | |
| 78 | const capK = capellaRows.reduce((s, r) => s + (r.score?.k ?? 0), 0); |
| 79 | const capD = capellaRows.reduce((s, r) => s + (r.score?.d ?? 0), 0); |
| 80 | const proK = procyonRows.reduce((s, r) => s + (r.score?.k ?? 0), 0); |
| 81 | const proD = procyonRows.reduce((s, r) => s + (r.score?.d ?? 0), 0); |
| 82 | |
| 83 | const capellaEmoji = Emoji.get("capella"); |
| 84 | const procyonEmoji = Emoji.get("procyon"); |
| 85 | |
| 86 | const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNames, capellaScores, capellaKds, capellaAtks, capellaDefs)); |
| 87 | const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNames, procyonScores, procyonKds, procyonAtks, procyonDefs)); |
| 88 | |
| 89 | const embed = new EmbedBuilder() |
| 90 | .setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`) |
| 91 | .setColor(0xe8a317) |
| 92 | .setFooter({ text: `TG Result · ${TGKey.toDisplay(historyKey)}` }) |
| 93 | .setTimestamp(); |
| 94 | |
| 95 | EmbedHelpers.addPerPlayerColumn(embed, `${capellaEmoji} Capella${(capK || capD) ? ` — ${format.kd(capK, capD)}` : ""}`, capellaFormatted); |
| 96 | embed.addFields({ name: "\u200b", value: "\u200b", inline: false }); |
| 97 | EmbedHelpers.addPerPlayerColumn(embed, `${procyonEmoji} Procyon${(proK || proD) ? ` — ${format.kd(proK, proD)}` : ""}`, procyonFormatted); |
| 98 | |
| 99 | return embed; |
| 100 | } |