// ─── Branded primitive types ───────────────────────────────────────────────── export type UserKey = string; export type DiscordId = string; export type CharName = string; export type SlotHour = number; export type VoteType = "yes" | "no"; export type ConfirmType = "yes" | "no"; // ─── Nation ─────────────────────────────────────────────────────────────────── export enum Nation { Capella = "Capella", Procyon = "Procyon", } // ─── Class ──────────────────────────────────────────────────────────────────── export type ClassKey = | "BL" // Blader | "FB" // Force Blader | "FS" // Force Shielder | "FA" // Force Archer | "FG" // Force Gunner | "GL" // Gladiator | "DM" // Dark Mage | "WI" // Wizard | "WA"; // Warrior export interface CharacterClass { key: ClassKey; name: string; // "Force Blader" shortName: string; // "FB" } export const CLASSES: Record = { FB: { key: "FB", name: "Force Blader", shortName: "FB" }, WI: { key: "WI", name: "Wizard", shortName: "WI" }, BL: { key: "BL", name: "Blader", shortName: "BL" }, FS: { key: "FS", name: "Force Shielder", shortName: "FS" }, FA: { key: "FA", name: "Force Archer", shortName: "FA" }, FG: { key: "FG", name: "Force Gunner", shortName: "FG" }, GL: { key: "GL", name: "Gladiator", shortName: "GL" }, DM: { key: "DM", name: "Dark Mage", shortName: "DM" }, WA: { key: "WA", name: "Warrior", shortName: "WA" }, }; export const CLASS_NAMES: Record = { BL: "Blader", FB: "Force Blader", FS: "Force Shielder", FA: "Force Archer", FG: "Force Gunner", GL: "Gladiator", DM: "Dark Mage", WI: "Wizard", WA: "Warrior", }; // ─── Character ─────────────────────────────────────────────────────────────── export interface CharacterStats { str?: number; int?: number; dex?: number; honorRank?: number; honorPoints?: number; custom?: Record; } export interface Character { name: string; class: CharacterClass; level: number; nation: Nation; active?: boolean; ownerKey: UserKey; stats?: CharacterStats; sharedWith?: string[]; // usermap keys with permanent access } export interface BorrowRequest { requesterKey: string; // who wants to borrow ownerKey: string; // who owns the character charName: string; // which character requestedAt: number; // timestamp for expiry } // ─── Account ───────────────────────────────────────────────────────────────── export interface AccountData { collection?: Record; animaMastery?: Record; custom?: Record; } export interface AccountMap { [userKey: string]: AccountData; } // interface UserIdentity { // discordId: DiscordId; // never changes — primary key // discordUsername: string; // current username — may change // userKey: UserKey | null; // system key // lookupId: string; // discordId if ID-mapped, discordUsername if legacy // displayName: string; // serverNickname: string | null; // globalNickname: string | null; // aliases: string[]; // activeCharacter: Character | null; // } // ─── Usermap ───────────────────────────────────────────────────────────────── export interface UsermapEntry { file: string; aliases: string[]; } export interface Usermap { [discordUsername: string]: UsermapEntry | string; // string = legacy format } // ─── TG Slots ──────────────────────────────────────────────────────────────── export interface TGSlot { tgHour: number; // 20 pollOpens: string; // "10:00" closesAfter: number; // minutes after tgHour (default 35) active: boolean; } // ─── Poll ──────────────────────────────────────────────────────────────────── // export interface VoteEntry { // userKey: string; // displayName: string; // server nickname → global nickname → username // characterName?: string; // active character name at time of vote // characterClass?: ClassKey; // snapshotted // characterLevel?: number; // snapshotted // characterNation?: Nation; // snapshotted at vote time // votedAt: string; // HH:MM formatted // previousYesAt?: string; // previousNoAt?: string; // publicMessage?: string; // resolved from message system or officer override // publicMessageOverride?: string;// set by officer via /tg poll set-message // ephemeralOverride?: string; // set by officer via /tg poll set-ephemeral // borrowedFrom?: string // Borrowed character from who // discordId?: string; // real Discord ID of the voter (for notifications) // } export interface VoteEntry { userKey?: UserKey; displayName?: string; characterName?: CharName; characterClass?: ClassKey; characterLevel?: number; characterNation?: Nation; borrowedFrom?: UserKey; discordId?: DiscordId; votedAt?: string; publicMessage?: string; previousYesAt?: string; previousNoAt?: string; } export interface PollState { slot: SlotHour; messageId?: string | null; locked: boolean; confirmed?: "yes" | "no" | null; yes: Map; no: Map; lockedYesKeys?: Set; lockMessage?: string; confirmMessage?: string; called?: boolean; calledAt?: string; } // ─── Scores ────────────────────────────────────────────────────────────────── 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 TGStats { atk?: number; def?: number; heal?: number; } // ─── TG Result ─────────────────────────────────────────────────────────────── export interface NationKD { k: number; d: number; } export interface TGResult { slot: number; date: string; // YYYY-MM-DD closedAt?: string; // ISO timestamp confirmed: boolean; nationKD: { source: Nation; // which nation is source of truth capella: NationKD; procyon: NationKD; }; scores: TGScore[]; } // ─── W.Rank ────────────────────────────────────────────────────────────────── // export interface WRankEntry { // character: Character; // weeklyPoints: number; // tgCount: number; // currentRank: number; // previousRank?: number; // } // interface UserIdentity { // discordId: DiscordId; // never changes — primary key // discordUsername: string; // current username — may change // userKey: UserKey | null; // system key // lookupId: string; // discordId if ID-mapped, discordUsername if legacy // displayName: string; // serverNickname: string | null; // globalNickname: string | null; // aliases: string[]; // activeCharacter: Character | null; // } export interface WRankPosition { currentRank: number; previousRank?: number; } // ─── Bringer ───────────────────────────────────────────────────────────────── export interface BringerState { currentWeek: string; // "2026-W22" capella: string | null; // userKey procyon: string | null; capellaOverride?: string; procyonOverride?: string; } // ─── Messages ──────────────────────────────────────────────────────────────── export interface MessageEntry { clicks: number; message?: string; // single message (legacy) messages?: string[]; // array of messages random?: boolean; days?: Record; dates?: Record; } export interface MessageBlock { yes: MessageEntry[]; no: MessageEntry[]; users?: Record; } export interface MessagesFile { public: MessageBlock; ephemeral: MessageBlock; } // ─── Emojis ────────────────────────────────────────────────────────────────── export interface EmojiMap { [key: string]: string; // e.g. "capella" → "<:Capella:1477082112560726238>" } // ─── Interaction context ───────────────────────────────────────────────────── export interface ResolvedUser { userId: string; discordUsername: string; // interaction.user.username userKey: string | null; // resolved from usermap lookupUsername: string | null; displayName: string; // server nickname → global nickname → username serverNickname: string | null; globalNickname: string | null; aliases: string[]; activeCharacter: Character | null; }