306 lines
No EOL
11 KiB
TypeScript
306 lines
No EOL
11 KiB
TypeScript
|
|
// ─── 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<ClassKey, CharacterClass> = {
|
|
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<ClassKey, string> = {
|
|
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<string, number>;
|
|
}
|
|
|
|
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<string, number>;
|
|
animaMastery?: Record<string, number>;
|
|
custom?: Record<string, number>;
|
|
}
|
|
|
|
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<string, VoteEntry>;
|
|
no: Map<string, VoteEntry>;
|
|
lockedYesKeys?: Set<UserKey>;
|
|
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<string, { messages: string[]; random?: boolean }>;
|
|
dates?: Record<string, { messages: string[]; random?: boolean }>;
|
|
}
|
|
|
|
export interface MessageBlock {
|
|
yes: MessageEntry[];
|
|
no: MessageEntry[];
|
|
users?: Record<string, { yes: MessageEntry[]; no: MessageEntry[] }>;
|
|
}
|
|
|
|
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;
|
|
} |