fix Bringer showing incorrectly from previous weeks on result post
This commit is contained in:
parent
772477e6e8
commit
7d68530826
4 changed files with 76 additions and 94 deletions
|
|
@ -18,19 +18,10 @@
|
||||||
import { format } from "@format";
|
import { format } from "@format";
|
||||||
import { Logger } from "@systems/logger";
|
import { Logger } from "@systems/logger";
|
||||||
import { DiscordClient } from "@discord/client";
|
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");
|
const log = Logger.for("result");
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
interface ResultRow {
|
|
||||||
character: Character;
|
|
||||||
score?: TGScore;
|
|
||||||
position?: { currentRank: number; previousRank?: number };
|
|
||||||
leavesCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Data ─────────────────────────────────────────────────────────────────────
|
// ─── Data ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function buildRows(historyKey: TGKey): ResultRow[] {
|
function buildRows(historyKey: TGKey): ResultRow[] {
|
||||||
|
|
@ -80,6 +71,7 @@
|
||||||
score,
|
score,
|
||||||
position,
|
position,
|
||||||
leavesCount: Leaves.countForChar({ characterName: char.name }),
|
leavesCount: Leaves.countForChar({ characterName: char.name }),
|
||||||
|
historyKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +95,10 @@
|
||||||
return;
|
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({
|
await PersistentMessage.post({
|
||||||
store: "results",
|
store: "results",
|
||||||
|
|
|
||||||
13
src/types.ts
13
src/types.ts
|
|
@ -177,19 +177,6 @@ export interface PollState {
|
||||||
calledAt?: string;
|
calledAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// export interface PollState {
|
|
||||||
// messageId: string | null;
|
|
||||||
// slot: number;
|
|
||||||
// yes: Map<string, VoteEntry>; // userId → VoteEntry
|
|
||||||
// no: Map<string, VoteEntry>;
|
|
||||||
// locked: boolean;
|
|
||||||
// confirmed: "yes" | "no" | null;
|
|
||||||
// lockMessage?: string;
|
|
||||||
// confirmMessage?: string;
|
|
||||||
// lockedYesKeys?: Set<string>; // snapshot of userKeys in yes at lock time
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ─── Scores ──────────────────────────────────────────────────────────────────
|
// ─── Scores ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface TGScore {
|
export interface TGScore {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@
|
||||||
import { Config } from "@systems/config";
|
import { Config } from "@systems/config";
|
||||||
import { Logger } from "@systems/logger";
|
import { Logger } from "@systems/logger";
|
||||||
import { Runtime } from "@systems/runtime";
|
import { Runtime } from "@systems/runtime";
|
||||||
import { TGStats } from "@root/src/types";
|
import { Character, TGScore, TGStats } from "@root/src/types";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { WRankWeek } from "@root/src/systems/wrank";
|
||||||
|
|
||||||
const log = Logger.for("result-ui");
|
const log = Logger.for("result-ui");
|
||||||
|
|
||||||
|
|
@ -19,20 +20,8 @@
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface ResultRow {
|
export interface ResultRow {
|
||||||
character: {
|
character: Character;
|
||||||
name: string;
|
score?: TGScore;
|
||||||
class: any;
|
|
||||||
level: number;
|
|
||||||
nation: any;
|
|
||||||
ownerKey: string;
|
|
||||||
};
|
|
||||||
score?: {
|
|
||||||
pts: number;
|
|
||||||
k?: number;
|
|
||||||
d?: number;
|
|
||||||
stats?: TGStats;
|
|
||||||
wRankAtSubmission?: { rank: number; delta: number };
|
|
||||||
};
|
|
||||||
position?: { currentRank: number; previousRank?: number };
|
position?: { currentRank: number; previousRank?: number };
|
||||||
leavesCount: number;
|
leavesCount: number;
|
||||||
historyKey: TGKey;
|
historyKey: TGKey;
|
||||||
|
|
@ -41,7 +30,7 @@
|
||||||
export interface ResultLayout {
|
export interface ResultLayout {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder;
|
buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder;
|
||||||
formatRow(row: ResultRow, context: any): string;
|
formatRow(row: ResultRow, context: any): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,8 +96,8 @@
|
||||||
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const ResultUI = {
|
export const ResultUI = {
|
||||||
buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder {
|
buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder {
|
||||||
return activeLayout().buildEmbed(historyKey, rows);
|
return activeLayout().buildEmbed(historyKey, rows, week);
|
||||||
},
|
},
|
||||||
|
|
||||||
formatRow(row: ResultRow, context: any): string {
|
formatRow(row: ResultRow, context: any): string {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
import { EmbedHelpers } from "@ui/embed-helpers";
|
import { EmbedHelpers } from "@ui/embed-helpers";
|
||||||
import { TextAlign } from "@ui/text-align";
|
import { TextAlign } from "@ui/text-align";
|
||||||
import { ResultLayout, ResultRow } from "../index";
|
import { ResultLayout, ResultRow } from "../index";
|
||||||
|
import { WRankWeek } from "@root/src/systems/wrank";
|
||||||
|
|
||||||
const TEMPLATE = "{wrank} {class} {name}{indicators} {score} {kd}";
|
const TEMPLATE = "{wrank} {class} {name}{indicators} {score} {kd}";
|
||||||
|
|
||||||
|
|
@ -24,55 +25,59 @@
|
||||||
const HEAL_GAP = 4;
|
const HEAL_GAP = 4;
|
||||||
|
|
||||||
function formatRow(
|
function formatRow(
|
||||||
row: ResultRow,
|
row: ResultRow,
|
||||||
context: NationContext,
|
context: NationContext,
|
||||||
allNames: string[],
|
allNameBlocks: string[],
|
||||||
allScores: string[],
|
allScores: string[],
|
||||||
allKds: string[],
|
allKds: string[],
|
||||||
allAtks: string[],
|
allAtks: string[],
|
||||||
allDefs: string[]
|
allDefs: string[],
|
||||||
): string {
|
week: WRankWeek | null
|
||||||
const char = row.character;
|
): string {
|
||||||
const goal = Config.get({ section: "wrank", key: "goal" });
|
const char = row.character;
|
||||||
const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1);
|
const goal = Config.get({ section: "wrank", key: "goal" });
|
||||||
const classKey = (typeof char.class === "object" ? char.class?.key : char.class) as ClassKey;
|
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<string, string> = {
|
||||||
|
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") || "📊";
|
function buildEmbed(historyKey: TGKey, rows: ResultRow[], week: WRankWeek|null): EmbedBuilder {
|
||||||
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<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);
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
const { date, slot } = TGKey.parse(historyKey);
|
const { date, slot } = TGKey.parse(historyKey);
|
||||||
|
|
||||||
const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella);
|
const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella);
|
||||||
|
|
@ -86,8 +91,14 @@
|
||||||
const sortedCapella = [...capellaRows].sort(sortByScore);
|
const sortedCapella = [...capellaRows].sort(sortByScore);
|
||||||
const sortedProcyon = [...procyonRows].sort(sortByScore);
|
const sortedProcyon = [...procyonRows].sort(sortByScore);
|
||||||
|
|
||||||
const capellaNames = sortedCapella.map((r) => r.character.name);
|
const capellaNameBlocks = sortedCapella.map((r) => {
|
||||||
const procyonNames = sortedProcyon.map((r) => r.character.name);
|
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 capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
|
||||||
const procyonScores = sortedProcyon.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 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 capellaEmoji = Emoji.get("capella");
|
||||||
const procyonEmoji = Emoji.get("procyon");
|
const procyonEmoji = Emoji.get("procyon");
|
||||||
|
|
||||||
const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNames, capellaScores, capellaKds, capellaAtks, capellaDefs));
|
const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNameBlocks, capellaScores, capellaKds, capellaAtks, capellaDefs, week));
|
||||||
const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNames, procyonScores, procyonKds, procyonAtks, procyonDefs));
|
const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNameBlocks, procyonScores, procyonKds, procyonAtks, procyonDefs, week));
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)
|
.setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue