Última actividad 1 week ago

gistfile1.txt Sin formato
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 }