import * as z from "zod"; export interface CatalogConstellation { level: number; name: string; description: string; iconKey: string | null; } export interface CatalogTalent { type: "NA" | "E" | "Q"; name: string; description: string; iconKey: string | null; } export interface CatalogAscensionMaterial { key: string; amount: number; } export interface CatalogAscensionGroup { type: string; items: CatalogAscensionMaterial[]; } export type CatalogLevelingGroup = CatalogAscensionGroup; export interface CatalogMaterial { key: string; name: string; description: string | null; rarity: number | null; type: string | null; imageKey: string; } export interface CatalogBaseStat { key: string; value: number | string; } const constellationSchema = z.object({ name: z.string(), description: z.string(), icon: z.string(), }); const skillSchema = z.object({ name: z.string(), description: z.string(), icon: z.string(), }); const attributeSchema = z.record(z.string(), z.union([z.number(), z.string()])); const levelingSchema = z.record(z.string(), z.record(z.string(), z.number().int())); export const characterDetailSchema = z.object({ info: z.object({ element: z.string().optional(), weapon: z.string().optional(), attributes: z.array(attributeSchema).nullish(), }).nullish(), constellations: z.record(z.string(), constellationSchema).nullish(), skills: z.object({ normalattack: skillSchema.optional(), elementalskill: skillSchema.optional(), elementalburst: skillSchema.optional(), leveling: levelingSchema.optional(), }).nullish(), ascension: z.record( z.string(), z.union([z.number().int(), z.record(z.string(), z.number().int())]), ).nullish(), leveling: levelingSchema.nullish(), attributes: z.array(attributeSchema).nullish(), }); export function cleanLunarisText(value: string): string { return value .replace(/\\n/gu, "\n") .replace(/\{\/?LINK[^}]*\}/gu, "") .replace(/<\/?[a-z]+(?:=[^>]*)?>/giu, "") .replace(/^"|"$/gu, "") .trim(); } export function avatarIconName(value: string): string { return value.replace(/^UI_Gacha_AvatarIcon_/u, "UI_AvatarIcon_"); } function characterAssetName(value: string): string { return avatarIconName(value).replace(/^UI_AvatarIcon_/u, ""); } export function splashImageName(value: string): string { return `UI_Gacha_AvatarImg_${characterAssetName(value)}.png`; } export function nameCardImageName(value: string): string { return `UI_NameCardPic_${characterAssetName(value)}_P.png`; } export function supportsWeaponType( characterWeaponType: string | null | undefined, weaponType: string | null | undefined, ): boolean { return Boolean(characterWeaponType && characterWeaponType === weaponType); } export function constellationSnapshot( value: z.infer, iconKey: (icon: string) => string, ): CatalogConstellation[] { const result: CatalogConstellation[] = []; for (const index of Array.from({ length: 6 }, (_, itemIndex) => itemIndex)) { const level = index + 1; const item = value.constellations?.[`const${level}`]; if (!item) continue; result.push({ level, name: cleanLunarisText(item.name), description: cleanLunarisText(item.description), iconKey: iconKey(item.icon), }); } return result; } export function talentSnapshot( value: z.infer, iconKey: (icon: string) => string, ): CatalogTalent[] { const talents = [ ["NA", value.skills?.normalattack], ["E", value.skills?.elementalskill], ["Q", value.skills?.elementalburst], ] as const; return talents.flatMap(([type, talent]) => talent ? [{ type, name: cleanLunarisText(talent.name), description: cleanLunarisText(talent.description), iconKey: iconKey(talent.icon), }] : []); } export function ascensionSnapshot( value: z.infer, ): CatalogAscensionGroup[] { return Object.entries(value.ascension ?? {}).map(([type, entries]) => ({ type, items: typeof entries === "number" ? [{ key: type, amount: entries }] : Object.entries(entries).map(([key, amount]) => ({ key, amount })), })); } export function levelingSnapshot( value: z.infer, ): CatalogLevelingGroup[] { return Object.entries(value.skills?.leveling ?? value.leveling ?? {}).map(([type, entries]) => ({ type, items: Object.entries(entries).map(([key, amount]) => ({ key, amount })), })); } export function baseStatsSnapshot( value: z.infer, ): CatalogBaseStat[] { const level90 = (value.info?.attributes ?? value.attributes)?.find((entry) => entry.level === 90); if (!level90) return []; return Object.entries(level90) .filter(([key]) => key !== "level" && key !== "ascension") .map(([key, stat]) => ({ key, value: stat })); }