143 lines
5.8 KiB
TypeScript
143 lines
5.8 KiB
TypeScript
import * as z from "zod";
|
|
|
|
import { targetStatOptions } from "@/lib/guides/stats";
|
|
|
|
export const guideSectionKinds = ["weapon", "artifact", "constellations", "team"] as const;
|
|
export type GuideSectionKind = (typeof guideSectionKinds)[number];
|
|
|
|
const percent = z.coerce.number().finite().min(-9999).max(9999);
|
|
const catalogKey = z.string().trim().min(1).max(64);
|
|
|
|
export const createGuideSchema = z.strictObject({
|
|
characterKey: catalogKey,
|
|
coverMediaId: z.string().uuid(),
|
|
overview: z.string().trim().max(500),
|
|
});
|
|
|
|
export const updateGuideSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
coverMediaId: z.string().uuid(),
|
|
overview: z.string().trim().min(1).max(500),
|
|
});
|
|
|
|
export const weaponSectionSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
rows: z.array(z.strictObject({
|
|
weaponKey: catalogKey,
|
|
refinement: z.number().int().min(1).max(5),
|
|
overallPercent: percent.nullable(),
|
|
personalPercent: percent.nullable(),
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
}).refine((row) => row.overallPercent !== null || row.personalPercent !== null, {
|
|
message: "อาวุธต้องมีเปอร์เซ็นต์อย่างน้อยหนึ่งค่า",
|
|
})).max(100),
|
|
});
|
|
|
|
export const mainStatOptions = {
|
|
sands: ["EM", "ER", "ATK%", "HP%", "DEF%"],
|
|
goblet: ["EM", "ATK%", "DEF%", "HP%", "Pyro DMG%", "Hydro DMG%", "Cryo DMG%", "Electro DMG%", "Anemo DMG%", "Geo DMG%", "Dendro DMG%", "Physical DMG%"],
|
|
circlet: ["HP%", "ATK%", "DEF%", "EM", "CRIT Rate%/CRIT DMG%", "Healing Bonus%"],
|
|
} as const;
|
|
|
|
export const substatOptions = ["CRIT Rate%/CRIT DMG%", "HP%", "HP", "ATK%", "ATK", "DEF%", "DEF", "EM", "ER"] as const;
|
|
export const talentLevelOptions = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] as const;
|
|
export const talentRelationOptions = ["gt", "gte", "eq"] as const;
|
|
|
|
const artifactGroupSchema = (name: "Late Game" | "Early Game") => z.strictObject({
|
|
name: z.literal(name),
|
|
rows: z.array(z.strictObject({
|
|
firstArtifactKey: catalogKey,
|
|
secondArtifactKey: catalogKey.nullable(),
|
|
percent,
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
})).max(50),
|
|
});
|
|
|
|
export const artifactSectionSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
groups: z.tuple([
|
|
artifactGroupSchema("Late Game"),
|
|
artifactGroupSchema("Early Game"),
|
|
]),
|
|
sands: z.array(z.enum(mainStatOptions.sands)).max(3),
|
|
goblet: z.array(z.enum(mainStatOptions.goblet)).max(3),
|
|
circlet: z.array(z.enum(mainStatOptions.circlet)).max(3),
|
|
substats: z.array(z.enum(substatOptions)).max(substatOptions.length),
|
|
targets: z.array(z.strictObject({
|
|
stat: z.enum(targetStatOptions),
|
|
value: z.string().trim().min(1).max(80),
|
|
})).max(targetStatOptions.length),
|
|
talents: z.array(z.strictObject({
|
|
type: z.enum(["NA", "E", "Q"]),
|
|
priority: z.number().int().min(1).max(3),
|
|
level: z.enum(talentLevelOptions).optional(),
|
|
relationToNext: z.enum(talentRelationOptions).optional(),
|
|
})).max(3),
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
});
|
|
|
|
const constellationRowsSchema = z.array(z.strictObject({
|
|
level: z.number().int().min(1).max(6),
|
|
detail: z.string().trim().min(1).max(2_000),
|
|
cumulativePercent: percent,
|
|
isGreat: z.boolean(),
|
|
})).max(6).refine((rows) => new Set(rows.map((row) => row.level)).size === rows.length, {
|
|
message: "แต่ละเงื่อนไขต้องมีระดับกลุ่มดาวไม่ซ้ำกัน",
|
|
});
|
|
|
|
export const constellationSectionSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
groups: z.array(z.strictObject({
|
|
note: z.string().trim().max(2_000).nullable(),
|
|
rows: constellationRowsSchema,
|
|
})).max(20),
|
|
});
|
|
|
|
export const teamSectionSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
teams: z.array(z.strictObject({
|
|
title: z.string().trim().min(1).max(160),
|
|
rotationSeconds: z.coerce.number().finite().positive().max(9999),
|
|
combo: z.string().trim().min(1).max(2_000),
|
|
members: z.array(z.strictObject({
|
|
characterKey: catalogKey,
|
|
weaponKey: catalogKey.nullable(),
|
|
artifactKey: catalogKey.nullable(),
|
|
secondArtifactKey: catalogKey.nullable(),
|
|
constellation: z.number().int().min(0).max(6),
|
|
refinement: z.number().int().min(1).max(5),
|
|
damage: z.coerce.number().finite().nonnegative().max(999_999_999_999),
|
|
})).length(4).refine((members) => new Set(members.map((member) => member.characterKey)).size === 4, {
|
|
message: "Team ต้องมีตัวละครไม่ซ้ำกัน 4 ตัว",
|
|
}).refine((members) => members.some((member) => member.damage > 0), {
|
|
message: "ความเสียหายรวมต้องมากกว่า 0",
|
|
}),
|
|
})).max(30),
|
|
});
|
|
|
|
export const extraSectionSchema = z.strictObject({
|
|
expectedVersion: z.number().int().positive(),
|
|
sections: z.array(z.strictObject({
|
|
title: z.string().trim().min(1).max(160),
|
|
rows: z.array(z.strictObject({
|
|
mediaId: z.string().uuid().nullable(),
|
|
ratio: z.enum(["1:1", "16:9"]),
|
|
heading: z.string().trim().min(1).max(200),
|
|
body: z.string().trim().max(2_000),
|
|
percent: percent.nullable(),
|
|
})).max(50),
|
|
})).max(20),
|
|
});
|
|
|
|
export type WeaponSectionInput = z.output<typeof weaponSectionSchema>;
|
|
export type ArtifactSectionInput = z.output<typeof artifactSectionSchema>;
|
|
export type ConstellationSectionInput = z.output<typeof constellationSectionSchema>;
|
|
export type TeamSectionInput = z.output<typeof teamSectionSchema>;
|
|
|
|
export function slugFromCharacterName(name: string, key: string): string {
|
|
const slug = name.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
return slug || `character-${key.toLowerCase()}`;
|
|
}
|