Ostatnio aktywny 3 weeks ago

wrank.ts Surowy
1import { Character, CharacterClass, Nation } from "@src/types";
2import { Emoji } from "@systems/emojis";
3import { WRankEntry } from "@systems/wrank";
4
5// ─── Individual formatters ────────────────────────────────────────────────────
6
7export interface CharDisplayOptions {
8 emoji?: boolean; // show class emoji (default: true)
9 level?: boolean; // show level (default: true)
10}
11
12function charButton(c: Character, options?: { shared?: boolean }): string {
13 const sharedMark = options?.shared ? " 🔗" : "";
14 return `${c.level} ${c.name}${sharedMark}`;
15}
16
17/**
18 * Format a character for display in embeds and messages.
19 * Output: <:class:> 79 «Flash»
20 */
21 function char(c: Character, options?: CharDisplayOptions): string {
22 const showEmoji = options?.emoji ?? true;
23 const showLevel = options?.level ?? true;
24 const classKey = c.class.key;
25 const classStr = showEmoji ? (Emoji.class(classKey) || classKey) : classKey;
26 const levelStr = showLevel ? `${c.level} ` : "";
27 return `${classStr} ${levelStr}${c.name}`.trim();
28}
29
30/**
31 * Format a nation name with its emoji.
32 * Output: <:capella:> Capella
33 */
34function nation(n: Nation): string {
35 const emoji = Emoji.nation(n);
36 return emoji ? `${emoji} ${n}` : n;
37}
38
39/**
40 * Format a score line.
41 * Output: <:score:> 3000 <:kd:> 32/18
42 */
43function score(pts: number, k?: number, d?: number): string {
44 const scoreEmoji = Emoji.get("score") || "📊";
45 const kdEmoji = Emoji.get("kd") || "⚔️";
46 const kdStr = k !== undefined && d !== undefined ? ` ${kdEmoji} ${k}/${d}` : "";
47 return `${scoreEmoji} ${pts}${kdStr}`;
48}
49
50/**
51 * Parse a Discord custom emoji string to an object for ButtonBuilder.setEmoji()
52 * Input: "<:fb:1511020923510194428>"
53 * Output: { name: "fb", id: "1511020923510194428" }
54 */
55function emoji(emojiStr: string): { name: string; id: string } | string | null {
56 if (!emojiStr) return null;
57 const match = emojiStr.match(/^<:(\w+):(\d+)>$/);
58 if (match) return { name: match[1], id: match[2] };
59 return emojiStr;
60}
61
62// ─── W.Rank formatters ────────────────────────────────────────────────────────
63
64export interface WRankDisplayOptions {
65 goal: number;
66 brackets?: boolean; // wrap delta in parentheses (default: true)
67}
68
69/**
70 * Format the rank indicator for a wrank entry.
71 * Output: <:wrank_1_gold:> or <:wrank_1:> or bold/plain number fallback
72 */
73function wrankRank(entry: WRankEntry, goal: number): string {
74 const isDone = entry.tgCount >= goal;
75 const rankKey = isDone ? `wrank_${entry.currentRank}_gold` : `wrank_${entry.currentRank}`;
76 return Emoji.get(rankKey) || (isDone ? `**${entry.currentRank}**` : `${entry.currentRank}`);
77}
78
79/**
80 * Format the delta indicator for a wrank entry.
81 * Output: <:wrank_up:><:wrank_up_2:> or ↑2, empty string if no change
82 */
83function wrankDelta(entry: WRankEntry, options?: { brackets?: boolean }): string {
84 const brackets = options?.brackets ?? true;
85 const prev = entry.previousRank;
86 const delta = prev !== undefined ? entry.currentRank - prev : 0;
87
88 if (delta === 0 && prev === undefined) return "";
89
90 let inner: string;
91 if (delta < 0) {
92 const abs = Math.abs(delta);
93 const numEmoji = Emoji.get(`wrank_up_${abs}`);
94 inner = (Emoji.get("wrank_up") || "↑") + (numEmoji || abs);
95 } else if (delta > 0) {
96 const numEmoji = Emoji.get(`wrank_down_${delta}`);
97 inner = (Emoji.get("wrank_down") || "↓") + (numEmoji || delta);
98 } else {
99 inner = (Emoji.get("wrank_no_dash") || "·") + (Emoji.get("wrank_neutral_0") || "0");
100 }
101
102 return brackets ? ` (${inner})` : ` ${inner}`;
103}
104
105/**
106 * Format a full wrank display string: rank + delta.
107 * Output: <:wrank_1_gold:> (<:wrank_up:><:wrank_up_2:>)
108 */
109function wrankFull(entry: WRankEntry, options: WRankDisplayOptions): string {
110 return wrankRank(entry, options.goal) + wrankDelta(entry, { brackets: options.brackets });
111}
112
113/**
114 * Placeholder for characters with no W.Rank when others in their nation have one.
115 * Output: — ( — 0 )
116 */
117 function wrankNoRank(): string {
118 const norank = Emoji.get("wrank_no_dash") || "—";
119 const dash = Emoji.get("wrank_no_rank_delta") || "—";
120 const square = Emoji.get("wrank_no_dash") || "■";
121 return `${norank} (${square}${dash})`;
122}
123
124// ─── Bringer formatters ────────────────────────────────────────────────────────
125
126function bringerDisplay(n: Nation): string {
127 const bringerMap: Record<Nation, string> = {
128 Capella: Emoji.get("luminous_bringer") || "🔆 Luminous Bringer",
129 Procyon: Emoji.get("storm_bringer") || "⚡ Storm Bringer",
130 };
131 return bringerMap[n];
132}
133
134// ─── Namespace export ─────────────────────────────────────────────────────────
135
136export const format = {
137 char,
138 charButton,
139 nation,
140 score,
141 emoji,
142 wrank: {
143 rank: wrankRank,
144 delta: wrankDelta,
145 full: wrankFull,
146 noRank: wrankNoRank,
147 },
148 bringer: bringerDisplay,
149};