feat : Con more reaible

This commit is contained in:
2026-09-26 20:54:40 +07:00 Unverified
parent bfd9f1765d
commit 17c4f21948
11 changed files with 325 additions and 24 deletions
+119
View File
@@ -4,9 +4,25 @@ export interface CatalogConstellation {
level: number;
name: string;
description: string;
descriptionNodes?: CatalogTextNode[];
iconKey: string | null;
}
export interface CatalogTextDetail {
name: string;
description: string;
}
export type CatalogTextNode =
| { type: "text"; value: string }
| { type: "color"; value: string; children: CatalogTextNode[] }
| {
type: "link";
target: string;
detail: CatalogTextDetail | null;
children: CatalogTextNode[];
};
export interface CatalogTalent {
type: "NA" | "E" | "Q";
name: string;
@@ -52,6 +68,12 @@ const skillSchema = z.object({
icon: z.string(),
});
const hyperlinkSchema = z.object({
id: z.number().int(),
name: z.string(),
description: 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()));
@@ -62,6 +84,7 @@ export const characterDetailSchema = z.object({
attributes: z.array(attributeSchema).nullish(),
}).nullish(),
constellations: z.record(z.string(), constellationSchema).nullish(),
hyperlinks: z.array(hyperlinkSchema).nullish(),
skills: z.object({
normalattack: skillSchema.optional(),
elementalskill: skillSchema.optional(),
@@ -85,6 +108,79 @@ export function cleanLunarisText(value: string): string {
.trim();
}
const lunarisTagPattern = /(\{LINK#[^}]+\}|\{\/LINK\}|<color=#[0-9a-f]{6}(?:[0-9a-f]{2})?>|<\/color>|<[^>]+>)/giu;
function textFromNodes(nodes: CatalogTextNode[]): string {
return nodes.map((node) =>
node.type === "text" ? node.value : textFromNodes(node.children)
).join("");
}
export function parseLunarisText(
value: string,
linkDetails: ReadonlyMap<string, CatalogTextDetail> = new Map(),
namedDetails: ReadonlyMap<string, CatalogTextDetail> = new Map(),
): CatalogTextNode[] {
const appendText = (children: CatalogTextNode[], text: string) => {
const previous = children.at(-1);
if (previous?.type === "text") previous.value += text;
else children.push({ type: "text", value: text });
};
const root: CatalogTextNode[] = [];
const stack: Array<{
type: "root" | "color" | "link";
children: CatalogTextNode[];
node?: Extract<CatalogTextNode, { type: "color" | "link" }>;
}> = [{ type: "root", children: root }];
const source = value
.replace(/\\n/gu, "\n")
.replace(/^"|"$/gu, "");
let offset = 0;
for (const match of source.matchAll(lunarisTagPattern)) {
const index = match.index;
const current = stack.at(-1)!;
if (index > offset) {
appendText(current.children, source.slice(offset, index));
}
const tag = match[0];
const link = /^\{LINK#([^}]+)\}$/iu.exec(tag);
const color = /^<color=(#[0-9a-f]{6}(?:[0-9a-f]{2})?)>$/iu.exec(tag);
if (link) {
const node: Extract<CatalogTextNode, { type: "link" }> = {
type: "link",
target: link[1],
detail: linkDetails.get(link[1]) ?? null,
children: [],
};
current.children.push(node);
stack.push({ type: "link", children: node.children, node });
} else if (color) {
const node: Extract<CatalogTextNode, { type: "color" }> = {
type: "color",
value: color[1],
children: [],
};
current.children.push(node);
stack.push({ type: "color", children: node.children, node });
} else if (tag.toUpperCase() === "{/LINK}" && stack.at(-1)?.type === "link") {
const frame = stack.pop();
if (frame?.node?.type === "link" && !frame.node.detail) {
frame.node.detail = namedDetails.get(textFromNodes(frame.children).trim()) ?? null;
}
} else if (tag.toLowerCase() === "</color>" && stack.at(-1)?.type === "color") {
stack.pop();
}
offset = index + tag.length;
}
if (offset < source.length) {
appendText(stack.at(-1)!.children, source.slice(offset));
}
return root;
}
export function avatarIconName(value: string): string {
return value.replace(/^UI_Gacha_AvatarIcon_/u, "UI_AvatarIcon_");
}
@@ -113,6 +209,28 @@ export function constellationSnapshot(
iconKey: (icon: string) => string,
): CatalogConstellation[] {
const result: CatalogConstellation[] = [];
const linkDetails = new Map(
(value.hyperlinks ?? []).map((item) => [
`N${item.id}`,
{
name: cleanLunarisText(item.name),
description: cleanLunarisText(item.description),
},
]),
);
const namedDetails = new Map(
[
value.skills?.normalattack,
value.skills?.elementalskill,
value.skills?.elementalburst,
].flatMap((skill) => skill ? [[
cleanLunarisText(skill.name),
{
name: cleanLunarisText(skill.name),
description: cleanLunarisText(skill.description),
},
] as const] : []),
);
for (const index of Array.from({ length: 6 }, (_, itemIndex) => itemIndex)) {
const level = index + 1;
const item = value.constellations?.[`const${level}`];
@@ -121,6 +239,7 @@ export function constellationSnapshot(
level,
name: cleanLunarisText(item.name),
description: cleanLunarisText(item.description),
descriptionNodes: parseLunarisText(item.description, linkDetails, namedDetails),
iconKey: iconKey(item.icon),
});
}