/** * Score — manages TG score submission and retrieval. * * Usage: * import { Score } from "@systems/score"; * * Score.get({ character, slot, date }) * Score.getWeeklySummary({ userKey }) * Score.submit({ character, borrowedFrom, pts, k, d, slot }) */ import { Character, Nation, UserKey, SlotHour, TGStats, TGScore } from "@types"; import { WRank } from "@systems/wrank"; import { Store } from "@systems/store"; import { Paths } from "@helpers/paths"; import { TGKey } from "@systems/tg-key"; import { RuntimeEvents } from "@systems/runtime"; // export interface TGScore { // userKey: UserKey; // playedBy?: UserKey; // if borrowed // characterName: string; // class: string; // nation: Nation; // pts: number; // k?: number; // d?: number; // stats?: TGStats; // submittedAt: string; // slot: SlotHour; // date: string; // submittedByOfficer: boolean; // wRankAtSubmission?: { // rank: number; // delta: number; // }; // } export interface WeeklySummary { userKey: UserKey; character: Character; scores: TGScore[]; totalPts: number; totalK: number; totalD: number; tgCount: number; currentRank?: number; previousRank?: number; } function getHistoryPath(historyKey: TGKey): string { return Paths.data("tg-history", `${historyKey}.json`); } function loadHistory(historyKey: TGKey): { scores: TGScore[] } { return Store.readOrDefault(getHistoryPath(historyKey), { scores: [] }); } function saveHistory(historyKey: TGKey, data: { scores: TGScore[] }): void { Store.write(getHistoryPath(historyKey), data); } export const Score = { /** * Get a score for a character in a specific TG. */ get({ character, slot, historyKey }: { character: Character; slot: SlotHour; historyKey?: TGKey; }): TGScore | null { const key = historyKey ?? TGKey.current({ slot }); const history = loadHistory(key); return history.scores.find( (s) => s.userKey === character.ownerKey && s.characterName === character.name ) ?? null; }, /** * Get weekly summary for a character. */ getWeeklySummary({ character }: { character: Character }): WeeklySummary { const week = WRank.currentWeek(); const entry = WRank.entry(character.name, character.nation); const scores: TGScore[] = []; for (const historyKey of (week.scoreIndex[character.name] ?? [])) { const history = loadHistory(historyKey as TGKey); const score = history.scores.find( (s) => s.userKey === character.ownerKey && s.characterName === character.name ); if (score) scores.push(score); } const totalPts = scores.reduce((sum, s) => sum + s.pts, 0); const totalK = scores.reduce((sum, s) => sum + (s.k ?? 0), 0); const totalD = scores.reduce((sum, s) => sum + (s.d ?? 0), 0); return { userKey: character.ownerKey, character, scores, totalPts, totalK, totalD, tgCount: scores.length, currentRank: entry?.currentRank, previousRank: entry?.previousRank, }; }, /** * Submit a score for a character. * Handles W.Rank snapshot at submission time. */ async submit({ character, playedBy, pts, k, d, atk, def, heal, slot, date, submittedByOfficer }: { character: Character; playedBy?: UserKey; pts: number; k?: number; d?: number; atk?: number; def?: number; heal?: number; slot: SlotHour; date?: string; // ← NEW, optional, defaults to today submittedByOfficer?: boolean; }): Promise { const resolvedDate = date ?? new Date().toISOString().slice(0, 10); const historyKey = TGKey.from({ date: resolvedDate, slot }); const history = loadHistory(historyKey); const existingEntry = WRank.entry(character.name, character.nation); const wRankAtSubmission = existingEntry ? { rank: existingEntry.currentRank, delta: existingEntry.currentRank - (existingEntry.previousRank ?? existingEntry.currentRank), } : undefined; const score: TGScore = { userKey: character.ownerKey, playedBy: playedBy, characterName: character.name, class: character.class.key, nation: character.nation, pts, k, d, stats: atk !== undefined || def !== undefined || heal !== undefined ? { atk, def, heal } : undefined, submittedAt: new Date().toISOString(), slot, date: resolvedDate, submittedByOfficer: submittedByOfficer ?? false, wRankAtSubmission, }; history.scores = history.scores.filter( (s) => !(s.userKey === character.ownerKey && s.characterName === character.name && s.slot === slot && s.date === resolvedDate) ); history.scores.push(score); saveHistory(historyKey, history); WRank.recordScore( character.ownerKey, character.name, character.class.key, character.nation, pts, historyKey ); await RuntimeEvents.emit("scoreSubmitted", { historyKey, character }); }, };