From 7d685308268ab986a5c7f7035c1d9256a42168f2 Mon Sep 17 00:00:00 2001 From: Nuno Duque Nunes Date: Mon, 22 Jun 2026 15:41:28 +0100 Subject: [PATCH] fix Bringer showing incorrectly from previous weeks on result post --- src/systems/result.ts | 17 ++-- src/types.ts | 13 ---- src/ui/result/index.ts | 25 ++---- src/ui/result/layouts/sequential.ts | 115 +++++++++++++++------------- 4 files changed, 76 insertions(+), 94 deletions(-) diff --git a/src/systems/result.ts b/src/systems/result.ts index 9ad9908..954f5d6 100644 --- a/src/systems/result.ts +++ b/src/systems/result.ts @@ -18,19 +18,10 @@ import { format } from "@format"; import { Logger } from "@systems/logger"; import { DiscordClient } from "@discord/client"; - import { ResultUI, ResultRow as UIResultRow } from "@ui/result"; + import { ResultUI, ResultRow as UIResultRow, ResultRow } from "@ui/result"; const log = Logger.for("result"); - // ─── Types ──────────────────────────────────────────────────────────────────── - - interface ResultRow { - character: Character; - score?: TGScore; - position?: { currentRank: number; previousRank?: number }; - leavesCount: number; - } - // ─── Data ───────────────────────────────────────────────────────────────────── function buildRows(historyKey: TGKey): ResultRow[] { @@ -80,6 +71,7 @@ score, position, leavesCount: Leaves.countForChar({ characterName: char.name }), + historyKey }); } @@ -103,7 +95,10 @@ return; } - const embed = ResultUI.buildEmbed(historyKey, rows as any); + const { date } = TGKey.parse(historyKey); + const weekKey = WRank.weekKey(new Date(date)); + const week = WRank.weekFromKey(weekKey); + const embed = ResultUI.buildEmbed(historyKey, rows, week); await PersistentMessage.post({ store: "results", diff --git a/src/types.ts b/src/types.ts index 69d7387..c93a63f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -177,19 +177,6 @@ export interface PollState { calledAt?: string; } - -// export interface PollState { -// messageId: string | null; -// slot: number; -// yes: Map; // userId → VoteEntry -// no: Map; -// locked: boolean; -// confirmed: "yes" | "no" | null; -// lockMessage?: string; -// confirmMessage?: string; -// lockedYesKeys?: Set; // snapshot of userKeys in yes at lock time -// } - // ─── Scores ────────────────────────────────────────────────────────────────── export interface TGScore { diff --git a/src/ui/result/index.ts b/src/ui/result/index.ts index ca90294..b808745 100644 --- a/src/ui/result/index.ts +++ b/src/ui/result/index.ts @@ -8,9 +8,10 @@ import { Config } from "@systems/config"; import { Logger } from "@systems/logger"; import { Runtime } from "@systems/runtime"; - import { TGStats } from "@root/src/types"; + import { Character, TGScore, TGStats } from "@root/src/types"; import path from "path"; import fs from "fs"; +import { WRankWeek } from "@root/src/systems/wrank"; const log = Logger.for("result-ui"); @@ -19,20 +20,8 @@ // ─── Types ──────────────────────────────────────────────────────────────────── export interface ResultRow { - character: { - name: string; - class: any; - level: number; - nation: any; - ownerKey: string; - }; - score?: { - pts: number; - k?: number; - d?: number; - stats?: TGStats; - wRankAtSubmission?: { rank: number; delta: number }; - }; + character: Character; + score?: TGScore; position?: { currentRank: number; previousRank?: number }; leavesCount: number; historyKey: TGKey; @@ -41,7 +30,7 @@ export interface ResultLayout { name: string; description: string; - buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder; + buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder; formatRow(row: ResultRow, context: any): string; } @@ -107,8 +96,8 @@ // ─── Dispatcher ─────────────────────────────────────────────────────────────── export const ResultUI = { - buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder { - return activeLayout().buildEmbed(historyKey, rows); + buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder { + return activeLayout().buildEmbed(historyKey, rows, week); }, formatRow(row: ResultRow, context: any): string { diff --git a/src/ui/result/layouts/sequential.ts b/src/ui/result/layouts/sequential.ts index c522ed0..2d5fa78 100644 --- a/src/ui/result/layouts/sequential.ts +++ b/src/ui/result/layouts/sequential.ts @@ -15,6 +15,7 @@ import { EmbedHelpers } from "@ui/embed-helpers"; import { TextAlign } from "@ui/text-align"; import { ResultLayout, ResultRow } from "../index"; +import { WRankWeek } from "@root/src/systems/wrank"; const TEMPLATE = "{wrank} {class} {name}{indicators} {score} {kd}"; @@ -24,55 +25,59 @@ const HEAL_GAP = 4; 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; + row: ResultRow, + context: NationContext, + allNameBlocks: string[], + allScores: string[], + allKds: string[], + allAtks: string[], + allDefs: string[], + week: WRankWeek | null +): 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) : "—"; + + const scoreColumn = [...allScores, ...allAtks]; + const kdColumn = [...allKds, ...allDefs]; + + const bringerTag = Layout.bringer(char as any, week ?? undefined); + const nameBlock = bringerTag ? `${char.name}${TextAlign.gap(1)}${bringerTag}` : char.name; + const paddedBlock = TextAlign.padToMax(nameBlock, allNameBlocks); + const cockroach = Layout.cockroach(char as any, row.historyKey); + + const tokens: Record = { + wrank: Layout.wrank(wrEntry, goal, context), + class: Emoji.class(classKey) || classKey || "?", + name: paddedBlock, + indicators: cockroach, + score: `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`, + kd: TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn), + }; + + const mainLine = Layout.formatRow(TEMPLATE, tokens); + + const stats = row.score?.stats; + const hasStats = !!stats && !!(stats.atk || stats.def || stats.heal); + 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", stats!.atk, "⚔️"); + const defText = format.statText("anima_def", stats!.def, "🛡️"); + const healText = format.statText("circle_massheal_purple", stats!.heal, "💚"); + + const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMax(defText, kdColumn)} ${TextAlign.gap(HEAL_GAP)}${healText}`; + + return `${mainLine}\n${statsLine}`; +} - 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) : "—"; - - const scoreColumn = [...allScores, ...allAtks]; - const kdColumn = [...allKds, ...allDefs]; - - const tokens: Record = { - 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); - - // Read stats from the NESTED shape (row.score.stats), matching TGScore's - // actual canonical structure. - const stats = row.score?.stats; - const hasStats = !!stats && !!(stats.atk || stats.def || stats.heal); - 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", stats!.atk, "⚔️"); - const defText = format.statText("anima_def", stats!.def, "🛡️"); - const healText = format.statText("circle_massheal_purple", stats!.heal, "💚"); - - const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMax(defText, kdColumn)} ${TextAlign.gap(HEAL_GAP)}${healText}`; - - return `${mainLine}\n${statsLine}`; - } - - function buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder { + function buildEmbed(historyKey: TGKey, rows: ResultRow[], week: WRankWeek|null): EmbedBuilder { const { date, slot } = TGKey.parse(historyKey); const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella); @@ -86,8 +91,14 @@ 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 capellaNameBlocks = sortedCapella.map((r) => { + const tag = Layout.bringer(r.character as any, week ?? undefined); + return tag ? `${r.character.name}${TextAlign.gap(1)}${tag}` : r.character.name; + }); + const procyonNameBlocks = sortedProcyon.map((r) => { + const tag = Layout.bringer(r.character as any, week ?? undefined); + return tag ? `${r.character.name}${TextAlign.gap(1)}${tag}` : 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) : "—"); @@ -108,8 +119,8 @@ 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 capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNameBlocks, capellaScores, capellaKds, capellaAtks, capellaDefs, week)); + const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNameBlocks, procyonScores, procyonKds, procyonAtks, procyonDefs, week)); const embed = new EmbedBuilder() .setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)