Files
buzz-sheet/lib/stygian/schema.ts
T
2026-10-01 00:13:38 +07:00

102 lines
4.1 KiB
TypeScript

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<typeof catalogItemSchema>) {
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<typeof catalogItemSchema>;
export type StygianRecommendationsInput = z.infer<typeof stygianRecommendationsSchema>;
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<typeof lunarisStygianScheduleSchema>) {
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 }] : [];
}),
}));
});
}