import * as z from "zod"; export const stygianLevels = [105, 110] as const; export const stygianElements = ["Anemo", "Geo", "Electro", "Dendro", "Hydro", "Pyro", "Cryo"] as const; const levelConfigSchema = z.object({ id: z.number().int(), specialMonsterIcon: z.string().min(1), monsterStats: z.object({ hp: z.number().finite().positive() }), enLevelName: z.string().min(1), enMonsterSpecialDesc: z.string().default(""), enMonsterSpecialMechanicName: z.array(z.string()), enLevelMaxDescription: z.array(z.string()), enRecommendedLevelMechanic: z.array(z.string()).default([]), tagType: z.array(z.enum(["Advantage", "Disadvantage", "None"])).default([]), }); export const lunarisStygianScheduleSchema = z.object({ scheduleId: z.number().int().positive(), challengeName: z.string().min(1), scheduleStartTime: z.string().min(1), scheduleEndTime: z.string().min(1), levels: z.array(z.object({ monsterLevel: z.number().int(), levelConfigs: z.array(levelConfigSchema), })), }); const characterRecommendationSchema = z.strictObject({ kind: z.literal("character"), catalogKey: z.string().min(1).max(64), weaponKey: z.string().min(1).max(64).nullable().default(null), artifactKey: z.string().min(1).max(64).nullable().default(null), constellationLevel: z.number().int().min(0).max(6).nullable().default(null), refinement: z.number().int().min(1).max(5).nullable().default(null), }); const catalogItemSchema = z.discriminatedUnion("kind", [ characterRecommendationSchema, z.strictObject({ kind: z.literal("weapon"), catalogKey: z.string().min(1).max(64) }), z.strictObject({ kind: z.literal("artifact"), catalogKey: z.string().min(1).max(64) }), z.strictObject({ kind: z.literal("element"), element: z.enum(stygianElements) }), ]); function recommendationKey(item: z.infer) { if (item.kind === "element") return `${item.kind}:${item.element}`; return `${item.kind}:${item.catalogKey}`; } const teamSchema = z.array(characterRecommendationSchema).min(1).max(4).refine( (items) => new Set(items.map(recommendationKey)).size === items.length, "รายการในทีมต้องไม่ซ้ำกัน", ); export const stygianRecommendationsSchema = z.strictObject({ expectedVersion: z.number().int().positive(), level: z.union([z.literal(105), z.literal(110)]), bossPosition: z.number().int().min(0).max(2), teams: z.array(teamSchema), units: z.array(catalogItemSchema).refine( (items) => new Set(items.map(recommendationKey)).size === items.length, "รายการตัวแนะนำต้องไม่ซ้ำกัน", ), }); export type StygianRecommendationItemInput = z.infer; export type StygianRecommendationsInput = z.infer; export function stygianImageName(icon: string): string { return icon .replace(/\.png$/iu, "") .replace(/^UI_MonsterIcon_/u, "UI_Img_LeyLineChallenge_"); } export function stygianImageFileName(icon: string): string { return `${stygianImageName(icon)}.png`; } export function selectedStygianBosses(value: z.infer) { return stygianLevels.flatMap((level) => { const source = value.levels.find((entry) => entry.monsterLevel === level); if (!source || source.levelConfigs.length !== 3) { throw new Error(`Stygian schedule ${value.scheduleId} must contain exactly three level ${level} bosses.`); } return source.levelConfigs.map((boss, position) => ({ level, position, configId: boss.id, name: boss.enLevelName, hp: boss.monsterStats.hp, description: boss.enMonsterSpecialDesc, imageName: stygianImageName(boss.specialMonsterIcon), recommendedMechanics: boss.enRecommendedLevelMechanic.map((entry) => entry.trim()), recommendedMechanicTypes: boss.tagType, mechanics: boss.enMonsterSpecialMechanicName.flatMap((name, index) => { const description = boss.enLevelMaxDescription[index]?.trim(); return name.trim() && description ? [{ name: name.trim(), description }] : []; }), })); }); }