import { type EquipmentSlotId, type ItemBuildProfile, type ItemCatalogEntry, type ItemRarity, type ItemStatProfile, type ItemUseProfile, type ItemWorldAffinity, type ItemWorldProfile, WorldType, } from "../types"; import { normalizeBuildRole, normalizeBuildTags } from "./buildTags"; export type ItemCategoryLabels = { weapon: string; armor: string; relic: string; consumable: string; material: string; rare: string; exclusive: string; }; type DesignedItemMetadata = Pick< ItemCatalogEntry, | "name" | "category" | "rarity" | "tags" | "description" | "worldAffinity" | "equipmentSlotId" | "worldProfiles" | "statProfile" | "useProfile" | "buildProfile" | "value" >; type MaterialTheme = { wuxia: string; xianxia: string; worldAffinity: ItemWorldAffinity; role: string; rarity: ItemRarity; setWuxia: string; setXianxia: string; tags: string[]; synergy: string[]; }; const MATERIAL_THEMES: Record = { Wooden: { wuxia: "乌木", xianxia: "灵木", worldAffinity: "neutral", role: "fieldcraft", rarity: "common", setWuxia: "山行木作", setXianxia: "灵木行旅", tags: ["探索", "制作"], synergy: ["探索", "采集", "过渡装备"], }, Copper: { wuxia: "赤铜", xianxia: "赤炼铜", worldAffinity: "wuxia", role: "breaker", rarity: "common", setWuxia: "赤铜开山", setXianxia: "赤炼破锋", tags: ["破甲", "爆发"], synergy: ["破甲", "前期开荒", "刚猛流"], }, Iron: { wuxia: "寒铁", xianxia: "玄铁", worldAffinity: "wuxia", role: "vanguard", rarity: "uncommon", setWuxia: "寒铁镇岳", setXianxia: "玄铁镇山", tags: ["先锋", "守卫"], synergy: ["承伤", "反击", "稳扎稳打"], }, Steel: { wuxia: "百炼钢", xianxia: "灵钢", worldAffinity: "neutral", role: "duelist", rarity: "rare", setWuxia: "百炼争锋", setXianxia: "灵钢斗枢", tags: ["决斗者", "节奏"], synergy: ["连击", "对拼", "压制"], }, Silver: { wuxia: "霜银", xianxia: "月银", worldAffinity: "xianxia", role: "ward", rarity: "rare", setWuxia: "霜银辟祟", setXianxia: "月银镇邪", tags: ["守卫", "灵体"], synergy: ["克制邪祟", "回复", "护体"], }, Gold: { wuxia: "鎏金", xianxia: "金霞", worldAffinity: "neutral", role: "fortune", rarity: "epic", setWuxia: "鎏金富贵", setXianxia: "金霞天赐", tags: ["财富", "支援"], synergy: ["经济", "爆发", "贵重馈赠"], }, Cobalt: { wuxia: "苍钴", xianxia: "苍穹钴晶", worldAffinity: "xianxia", role: "caster", rarity: "epic", setWuxia: "苍钴引雷", setXianxia: "钴晶御雷", tags: ["施法者", "法力"], synergy: ["法力", "远程", "雷系构筑"], }, Crimson: { wuxia: "绯钢", xianxia: "赤煞晶钢", worldAffinity: "wuxia", role: "berserker", rarity: "rare", setWuxia: "绯钢狂锋", setXianxia: "赤煞断岳", tags: ["狂战士", "爆发"], synergy: ["压血爆发", "破阵", "重击"], }, Altair: { wuxia: "星游", xianxia: "天狼星辉", worldAffinity: "xianxia", role: "assassin", rarity: "epic", setWuxia: "星游夜行", setXianxia: "星辉掠影", tags: ["刺客", "机动性"], synergy: ["身法", "暴击", "切后"], }, Adamantine: { wuxia: "玄钢", xianxia: "玄金陨铁", worldAffinity: "neutral", role: "fortress", rarity: "legendary", setWuxia: "玄钢不坏", setXianxia: "陨铁镇界", tags: ["堡垒", "坦克"], synergy: ["高承伤", "套装成型", "守中反打"], }, Angelic: { wuxia: "天辉", xianxia: "羽化天灵", worldAffinity: "xianxia", role: "paladin", rarity: "legendary", setWuxia: "天辉护心", setXianxia: "羽化圣辉", tags: ["圣骑士", "支援"], synergy: ["护盾", "回复", "圣光构筑"], }, Nova: { wuxia: "星火", xianxia: "星爆灵核", worldAffinity: "xianxia", role: "spellblade", rarity: "epic", setWuxia: "星火裂空", setXianxia: "星爆御剑", tags: ["法术之刃", "法力"], synergy: ["法武双修", "中距离压制", "星辰构筑"], }, Platinum: { wuxia: "白金", xianxia: "霜白灵金", worldAffinity: "neutral", role: "commander", rarity: "epic", setWuxia: "白金威仪", setXianxia: "灵金统御", tags: ["指挥官", "平衡"], synergy: ["全能", "队伍增益", "中后期构筑"], }, Fateful: { wuxia: "命纹", xianxia: "天命玄纹", worldAffinity: "xianxia", role: "fate", rarity: "legendary", setWuxia: "命纹转祸", setXianxia: "天命轮转", tags: ["命运", "实用"], synergy: ["冷却", "机缘", "运势构筑"], }, }; const ARMOR_PIECE_LABELS: Record< string, { wuxia: string; xianxia: string; pieceName: string; slot: EquipmentSlotId } > = { Boots: { wuxia: "踏云靴", xianxia: "凌霄履", pieceName: "boots", slot: "armor" }, Chestplate: { wuxia: "护心甲", xianxia: "灵铠", pieceName: "chest", slot: "armor" }, Gloves: { wuxia: "护腕", xianxia: "灵纹手甲", pieceName: "gloves", slot: "armor" }, Helmet: { wuxia: "冠盔", xianxia: "灵盔", pieceName: "helm", slot: "armor" }, Leggings: { wuxia: "行岳腿甲", xianxia: "踏虚护胫", pieceName: "leggings", slot: "armor" }, Shield: { wuxia: "镇势盾", xianxia: "护界灵盾", pieceName: "shield", slot: "armor" }, Weapon: { wuxia: "战兵", xianxia: "灵兵", pieceName: "weapon", slot: "weapon" }, }; const RARITY_ORDER: ItemRarity[] = ["common", "uncommon", "rare", "epic", "legendary"]; const GENERIC_TOKEN_LABELS: Record = { scroll: { wuxia: "秘卷", xianxia: "玉简" }, ring: { wuxia: "戒", xianxia: "灵戒" }, torch: { wuxia: "火把", xianxia: "明焰灯" }, helm: { wuxia: "盔", xianxia: "灵盔" }, helmet: { wuxia: "盔", xianxia: "灵盔" }, chest: { wuxia: "胸甲", xianxia: "灵铠" }, pants: { wuxia: "护腿", xianxia: "灵裤" }, boots: { wuxia: "靴", xianxia: "云履" }, gem: { wuxia: "宝石", xianxia: "灵晶" }, crystal: { wuxia: "晶簇", xianxia: "灵晶" }, cross: { wuxia: "镇煞十字", xianxia: "镇灵法印" }, potion: { wuxia: "药剂", xianxia: "灵液" }, water: { wuxia: "清水", xianxia: "灵泉水" }, bottle: { wuxia: "药瓶", xianxia: "灵瓶" }, neck: { wuxia: "项坠", xianxia: "灵坠" }, mushroom: { wuxia: "山菌", xianxia: "灵菌" }, meat: { wuxia: "肉脯", xianxia: "灵兽肉" }, apple: { wuxia: "果实", xianxia: "灵果" }, skull: { wuxia: "颅骨", xianxia: "骨印" }, bag: { wuxia: "行囊", xianxia: "乾坤袋" }, mace: { wuxia: "钉头锤", xianxia: "镇岳杵" }, spade: { wuxia: "铲", xianxia: "灵铲" }, coin: { wuxia: "铜钱", xianxia: "灵钱" }, stone: { wuxia: "石料", xianxia: "灵石料" }, wood: { wuxia: "木料", xianxia: "灵木材" }, glowes: { wuxia: "护手", xianxia: "灵纹手套" }, gloves: { wuxia: "护手", xianxia: "灵纹手套" }, book: { wuxia: "册页", xianxia: "道卷" }, leaf: { wuxia: "叶片", xianxia: "灵叶" }, sword: { wuxia: "剑", xianxia: "灵剑" }, bow: { wuxia: "弓", xianxia: "灵弓" }, arrow: { wuxia: "箭", xianxia: "灵矢" }, shield: { wuxia: "盾", xianxia: "灵盾" }, rope: { wuxia: "绳索", xianxia: "缚灵索" }, skin: { wuxia: "兽皮", xianxia: "妖皮" }, treasure: { wuxia: "宝匣", xianxia: "秘藏灵匣" }, pick: { wuxia: "鹤嘴镐", xianxia: "开脉灵镐" }, silverbar: { wuxia: "银锭", xianxia: "月银锭" }, flower: { wuxia: "花", xianxia: "灵花" }, wand: { wuxia: "法杖", xianxia: "灵杖" }, magic: { wuxia: "秘术核心", xianxia: "灵法结晶" }, }; function clampRarity(rank: number): ItemRarity { return RARITY_ORDER[Math.max(0, Math.min(RARITY_ORDER.length - 1, rank))] ?? "common"; } function rarityToRank(rarity: ItemRarity) { return RARITY_ORDER.indexOf(rarity); } function bumpRarity(rarity: ItemRarity, delta: number) { return clampRarity(rarityToRank(rarity) + delta); } function parseVariantIndex(normalizedSourcePath: string) { const match = normalizedSourcePath.match(/(\d+)(?=\.png$)/iu); return match ? Number(match[1]) : 1; } function buildWorldProfiles( wuxiaName: string, xianxiaName: string, wuxiaDescription: string, xianxiaDescription: string, ): Partial> { return { WUXIA: { name: wuxiaName, description: wuxiaDescription, }, XIANXIA: { name: xianxiaName, description: xianxiaDescription, }, }; } function dedupe(values: string[]) { return [...new Set(values.filter(Boolean))]; } function buildEquipmentStats( slot: EquipmentSlotId, rarity: ItemRarity, role: string, pieceName: string, ): ItemStatProfile { const rank = rarityToRank(rarity) + 1; if (slot === "weapon") { const outgoingDamageBonus = Number((0.04 + rank * 0.018).toFixed(2)); const maxManaBonus = role === "caster" || role === "spellblade" ? 8 + rank * 4 : 0; return { outgoingDamageBonus, maxManaBonus, maxHpBonus: role === "fortress" ? 8 + rank * 6 : 0, }; } const baseHp = pieceName === "shield" ? 16 + rank * 10 : pieceName === "chest" ? 14 + rank * 9 : pieceName === "helm" ? 10 + rank * 6 : 8 + rank * 5; const incomingDamageMultiplier = Number( Math.max(0.72, 0.99 - rank * 0.03 - (role === "fortress" ? 0.04 : 0)).toFixed(2), ); return { maxHpBonus: baseHp, maxManaBonus: role === "caster" || role === "paladin" ? 6 + rank * 4 : 0, outgoingDamageBonus: role === "duelist" || role === "berserker" || role === "assassin" ? Number((0.02 + rank * 0.01).toFixed(2)) : 0, incomingDamageMultiplier, }; } function buildRelicStats(rarity: ItemRarity, role: string): ItemStatProfile { const rank = rarityToRank(rarity) + 1; return { maxHpBonus: role === "ward" || role === "paladin" ? 8 + rank * 4 : 0, maxManaBonus: role === "caster" || role === "fate" || role === "support" ? 12 + rank * 6 : 6 + rank * 3, outgoingDamageBonus: role === "assassin" || role === "berserker" || role === "spellblade" ? Number((0.03 + rank * 0.012).toFixed(2)) : Number((0.01 + rank * 0.008).toFixed(2)), incomingDamageMultiplier: role === "ward" || role === "support" ? Number(Math.max(0.8, 0.98 - rank * 0.02).toFixed(2)) : undefined, }; } function buildBuildProfile( role: string, tags: string[], options: { setId?: string; setName?: string; pieceName?: string; synergy?: string[]; } = {}, ): ItemBuildProfile { return { role: normalizeBuildRole(role), tags: normalizeBuildTags([role, ...tags]), setId: options.setId, setName: options.setName, pieceName: options.pieceName, synergy: options.synergy ?? [], forgeRank: 0, }; } function buildItemBuildBuffs(sourceId: string, name: string, tags: string[], durationTurns: number) { return [{ id: `${sourceId}-buff`, sourceType: "item" as const, sourceId, name, tags: normalizeBuildTags(tags), durationTurns, }]; } function rankValue(rarity: ItemRarity, slot: EquipmentSlotId | null, useProfile: ItemUseProfile | null) { const rank = rarityToRank(rarity) + 1; let value = 18 + rank * 14; if (slot === "weapon") value += 22; if (slot === "armor") value += 18; if (slot === "relic") value += 20; if (useProfile?.hpRestore || useProfile?.manaRestore) value += 16; if (useProfile?.cooldownReduction) value += 22; return value; } function detectRoleFromDescriptor(descriptor: string) { const source = descriptor.toLowerCase(); if (/(wind|gust|nimble|rogue|hawk|arrow|sword_long|spear)/u.test(source)) return "assassin"; if (/(thunder|fierce|mighty|flame|serrated|punch|booster_iron|booster_steel)/u.test(source)) return "berserker"; if (/(arcane|esoteric|mage|ethereal|lunar|time|nightvision|copy|grimoire)/u.test(source)) return "caster"; if (/(fortitude|fortress|protector|shield|hearty|adaptable|honorable|cross)/u.test(source)) return "ward"; if (/(wedding|lovely|bud|oceanic|star|marbled|rich|vibrant|vivacious)/u.test(source)) return "support"; return "balanced"; } function buildGenericTokenName(token: string, worldType: WorldType) { const normalized = token.toLowerCase(); const mapped = GENERIC_TOKEN_LABELS[normalized]; if (mapped) { return worldType === WorldType.WUXIA ? mapped.wuxia : mapped.xianxia; } return token .replace(/\.[^.]+$/u, "") .replace(/_/g, " ") .trim(); } function buildLegacyDesign( normalizedSourcePath: string, name: string, category: string, rarity: ItemRarity, tags: string[], labels: ItemCategoryLabels, ): DesignedItemMetadata | null { if (normalizedSourcePath.includes("/")) return null; const baseToken = normalizedSourcePath .replace(/^\d+[_-]*/u, "") .replace(/\.png$/iu, "") .split(/[_-]/u) .filter(Boolean)[0] ?? name; const wuxiaName = buildGenericTokenName(baseToken, WorldType.WUXIA); const xianxiaName = buildGenericTokenName(baseToken, WorldType.XIANXIA); const slot: EquipmentSlotId | null = category === labels.weapon ? "weapon" : category === labels.armor ? "armor" : category === labels.relic ? "relic" : null; const useProfile: ItemUseProfile | null = category === labels.consumable || tags.includes("healing") || tags.includes("mana") ? { hpRestore: tags.includes("healing") ? 22 : 0, manaRestore: tags.includes("mana") ? 18 : 0, cooldownReduction: /power|mana|magic|bandage|torch/u.test(normalizedSourcePath) ? 1 : 0, } : null; const statProfile = slot === "weapon" ? buildEquipmentStats("weapon", rarity, "balanced", "weapon") : slot === "armor" ? buildEquipmentStats("armor", rarity, "balanced", "armor") : slot === "relic" ? buildRelicStats(rarity, "support") : null; return { name, category, rarity, tags: dedupe(tags), description: `${wuxiaName} / ${xianxiaName} 这件图标物资可在两套兼容模板中以不同风格登场,适合作为${category}基础模板继续扩展。`, worldAffinity: "neutral", equipmentSlotId: slot, worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName},适用于边城模板的基础${category}条目。`, `${xianxiaName},适用于灵潮模板的基础${category}条目。`, ), statProfile, useProfile, buildProfile: buildBuildProfile("starter", ["legacy", ...tags]), value: rankValue(rarity, slot, useProfile), }; } function buildArmoryDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata | null { const match = normalizedSourcePath.match( /Armory\/Singles\/(Armor Singles|Weapon Singles)\/([^/]+)\/([^/]+)\.png$/u, ); if (!match) return null; const family = match[2]; const filename = match[3]; if (!family || !filename) return null; const theme = MATERIAL_THEMES[family]; if (!theme) return null; const pieceMatch = filename.match(/_(Boots|Chestplate|Gloves|Helmet|Leggings|Shield|Weapon)(\d+)$/u); if (!pieceMatch) return null; const pieceKey = pieceMatch[1]; const variantIndex = Number(pieceMatch[2]); if (!pieceKey || Number.isNaN(variantIndex)) return null; const piece = ARMOR_PIECE_LABELS[pieceKey]; if (!piece) return null; const gradeTier = variantIndex >= 25 ? 2 : variantIndex >= 13 ? 1 : 0; const rarity: ItemRarity = bumpRarity(theme.rarity, gradeTier); const gradeWuxia = ["初式", "精铸", "真传"][gradeTier]; const gradeXianxia = ["凡品", "灵铸", "道印"][gradeTier]; const wuxiaName = `${theme.wuxia}${piece.wuxia}${gradeWuxia}`; const xianxiaName = `${theme.xianxia}${piece.xianxia}${gradeXianxia}`; const slot = piece.slot; const category = slot === "weapon" ? labels.weapon : labels.armor; const setId = `set-armory-${family.toLowerCase()}`; const setName = `${theme.setWuxia} / ${theme.setXianxia}`; const statProfile = slot === "weapon" ? buildEquipmentStats(slot, rarity, theme.role, piece.pieceName) : buildEquipmentStats(slot, rarity, theme.role, piece.pieceName); const tags = dedupe([ category === labels.weapon ? "weapon" : "armor", theme.worldAffinity, theme.role, ...theme.tags, `set:${family.toLowerCase()}`, `piece:${piece.pieceName}`, ]); return { name: theme.worldAffinity === "xianxia" ? xianxiaName : wuxiaName, category, rarity, tags, description: `${theme.setWuxia} / ${theme.setXianxia} 套装中的 ${piece.pieceName} 位。相邻编号代表同家族不同锻造阶段,适合围绕 ${theme.synergy.join("、")} 组 build。`, worldAffinity: theme.worldAffinity, equipmentSlotId: slot, worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName}来自${theme.setWuxia}套装,偏向${theme.synergy.join("、")}的边城模板 build。`, `${xianxiaName}属于${theme.setXianxia}套装,围绕${theme.synergy.join("、")}构筑灵潮模板战法。`, ), statProfile, useProfile: null, buildProfile: buildBuildProfile(theme.role, theme.tags, { setId, setName, pieceName: piece.pieceName, synergy: theme.synergy, }), value: rankValue(rarity, slot, null), }; } function buildJewelryDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata | null { const match = normalizedSourcePath.match( /Jewelry\/(Rings|Necklaces|Bracelets)\/Singles(?:\/[^/]+)*\/([^/]+)\.png$/u, ); if (!match) return null; const jewelryType = match[1]; const filename = match[2]; if (!jewelryType || !filename) return null; const descriptor = filename.replace(/^\d+_/u, ""); const role = detectRoleFromDescriptor(descriptor); const sizeTier = /Large|Fancy|Holder/u.test(descriptor) ? 2 : /Medium|Necklace|Bracelet/u.test(descriptor) ? 1 : 0; const rarity: ItemRarity = bumpRarity( sizeTier === 2 ? "rare" : sizeTier === 1 ? "uncommon" : "common", /Arcane|Esoteric|Lunar|Relic/u.test(descriptor) ? 1 : 0, ); const worldAffinity = role === "caster" ? "xianxia" : role === "berserker" || role === "assassin" ? "wuxia" : "neutral"; const baseWuxiaType = jewelryType === "Rings" ? "戒" : jewelryType === "Necklaces" ? "坠" : "镯"; const baseXianxiaType = jewelryType === "Rings" ? "灵戒" : jewelryType === "Necklaces" ? "灵坠" : "灵镯"; const leadingToken = descriptor.split("_").find(Boolean) ?? jewelryType; const wuxiaName = `${buildGenericTokenName(leadingToken, WorldType.WUXIA)}${baseWuxiaType}`; const xianxiaName = `${buildGenericTokenName(leadingToken, WorldType.XIANXIA)}${baseXianxiaType}`; const tags = dedupe(["relic", role, jewelryType.toLowerCase(), worldAffinity]); const setId = `set-jewelry-${role}`; return { name: worldAffinity === "xianxia" ? xianxiaName : wuxiaName, category: labels.relic, rarity, tags, description: `${jewelryType} 家族的 ${descriptor.replace(/_/g, " ")} 款式。围绕 ${role} build 提供核心词条,也可以与同角色定位的项链/手镯/戒指拼成饰品流派。`, worldAffinity, equipmentSlotId: "relic", worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName}偏向${role}向的武侠搭配,可作为饰品核心件。`, `${xianxiaName}更适合${role}向仙侠构筑,用于补足法力、爆发或护体短板。`, ), statProfile: buildRelicStats(rarity, role), useProfile: null, buildProfile: buildBuildProfile(role, tags, { setId, setName: `${role} 饰品系`, pieceName: jewelryType.toLowerCase(), synergy: ["饰品 build", "定向补短", "三件成型"], }), value: rankValue(rarity, "relic", null), }; } function buildPotionDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata | null { if (!/Potions\/Singles\//u.test(normalizedSourcePath)) return null; const filename = normalizedSourcePath.split("/").pop() ?? normalizedSourcePath; if (/Glass|Bottle_/u.test(filename) && !/Health|Mana|Pure|Essence|Soul/u.test(filename)) { return { name: "空药瓶", category: labels.material, rarity: "common", tags: ["material", "alchemy"], description: "炼药与装液容器,可作为配方材料或支线道具。", worldAffinity: "neutral", equipmentSlotId: null, worldProfiles: buildWorldProfiles( "药瓶", "灵瓶", "边城模板里常见的炼药容器。", "灵潮模板里常用的盛装灵液器皿。", ), statProfile: null, useProfile: null, buildProfile: buildBuildProfile("alchemy", ["material", "alchemy"]), value: 14, }; } const index = parseVariantIndex(normalizedSourcePath); const isHealth = /Health/u.test(filename); const isMana = /Mana/u.test(filename); const isPure = /Pure/u.test(filename); const isEssence = /Essence/u.test(filename); const isSoul = /Soul/u.test(filename); const rarity = isSoul ? "legendary" : isPure || isEssence ? "epic" : index > 118 ? "rare" : index > 108 ? "uncommon" : "common"; const gradeText = isSoul ? "封魂" : isPure ? "澄澈" : isEssence ? "萃华" : rarity === "rare" ? "上品" : rarity === "uncommon" ? "精制" : "常备"; const wuxiaName = isHealth ? `${gradeText}回春药` : isMana ? `${gradeText}养神露` : `${gradeText}奇药`; const xianxiaName = isHealth ? `${gradeText}补元灵液` : isMana ? `${gradeText}聚灵露` : `${gradeText}灵酿`; const useProfile: ItemUseProfile = { hpRestore: isHealth ? (isSoul ? 120 : isPure ? 82 : isEssence ? 64 : rarity === "rare" ? 44 : rarity === "uncommon" ? 28 : 18) : 0, manaRestore: isMana ? (isSoul ? 96 : isPure ? 70 : isEssence ? 54 : rarity === "rare" ? 38 : rarity === "uncommon" ? 24 : 16) : 0, cooldownReduction: isSoul || /Power/u.test(filename) ? 1 : 0, buildBuffs: isHealth ? buildItemBuildBuffs( `potion-${filename.replace(/\.png$/iu, "").toLowerCase()}`, "续战药势", ["回复", "续战"], isSoul ? 3 : 2, ) : isMana ? buildItemBuildBuffs( `potion-${filename.replace(/\.png$/iu, "").toLowerCase()}`, "聚灵药势", ["法力", "过载"], isSoul ? 3 : 2, ) : [], }; const tags = dedupe([ "alchemy", "consumable", isHealth ? "healing" : "", isMana ? "mana" : "", isSoul ? "legendary-tonic" : "", ]); return { name: wuxiaName, category: labels.consumable, rarity, tags, description: "同形药瓶按纯度和封装级别区分强度,越靠后的高阶药剂越适合核心战斗循环与极限保命。", worldAffinity: isMana || isSoul ? "xianxia" : "neutral", equipmentSlotId: null, worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName}常见于边城模板的远行行囊,用于快速续战或调息。`, `${xianxiaName}多用于灵潮模板的据点与试炼前后,负责补元、聚灵与压缩冷却。`, ), statProfile: null, useProfile, buildProfile: buildBuildProfile("alchemy", tags, { synergy: ["续航", "爆发前准备", "战中救急"], }), value: rankValue(rarity, null, useProfile), }; } function buildGemDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata | null { if (!/Gems(?: II)?\/Singles\//u.test(normalizedSourcePath)) return null; const filename = normalizedSourcePath.split("/").pop() ?? normalizedSourcePath; const tokenMatch = filename.match(/(Ruby|Onyx|Sapphire|Morganite|Emerald|Topaz|Amethyst|Diamond|Opal)/iu); const token = tokenMatch?.[1] ?? "Crystal"; const role = /Ruby|Crimson/u.test(token) ? "berserker" : /Sapphire|Amethyst|Morganite/u.test(token) ? "caster" : /Onyx|Diamond/u.test(token) ? "ward" : /Topaz|Opal/u.test(token) ? "assassin" : "support"; const rarity = /Dust/u.test(filename) ? "uncommon" : /Crystal/u.test(filename) ? "epic" : "rare"; const category = /Dust/u.test(filename) ? labels.material : labels.relic; const wuxiaName = `${buildGenericTokenName(token, WorldType.WUXIA)}${/Dust/u.test(filename) ? "碎屑" : /Crystal/u.test(filename) ? "晶魄" : "宝石"}`; const xianxiaName = `${buildGenericTokenName(token, WorldType.XIANXIA)}${/Dust/u.test(filename) ? "粉末" : /Crystal/u.test(filename) ? "灵髓" : "灵晶"}`; const tags = dedupe([ category === labels.material ? "material" : "relic", token.toLowerCase(), role, "socket", ]); return { name: category === labels.material ? wuxiaName : xianxiaName, category, rarity, tags, description: `${token} 系晶石适合做强度梯度:粉尘是材料,宝石是中阶插件,晶体是高阶核心件。`, worldAffinity: category === labels.relic ? "xianxia" : "neutral", equipmentSlotId: category === labels.relic ? "relic" : null, worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName}偏向边城模板里的匠造、镶嵌与兵刃锻造。`, `${xianxiaName}更适合灵潮模板里的灵器镶嵌与灵力 build 核心堆叠。`, ), statProfile: category === labels.relic ? buildRelicStats(rarity, role) : null, useProfile: null, buildProfile: buildBuildProfile(role, tags, { setId: `gem-${token.toLowerCase()}`, setName: `${token} 晶石谱系`, pieceName: /Dust/u.test(filename) ? "dust" : /Crystal/u.test(filename) ? "crystal" : "gem", synergy: ["镶嵌", "词条放大", "build 补强"], }), value: rankValue(rarity, category === labels.relic ? "relic" : null, null), }; } function buildSkillRelicDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata | null { if (!/Skills\/Singles\//u.test(normalizedSourcePath) && !/Librarium\/Singles\//u.test(normalizedSourcePath)) { return null; } const filename = normalizedSourcePath.split("/").pop() ?? normalizedSourcePath; const role = detectRoleFromDescriptor(filename); const isBookLike = /Book|Grimoire|Literature/u.test(filename); const isBooster = /Booster/u.test(filename); const isPassive = /Passive/u.test(filename); const isUtility = /Echolocation|Nightvision|Copy|Shout|Panic/u.test(filename); const category = isBooster ? labels.consumable : isBookLike || isPassive || isUtility ? labels.relic : labels.rare; const rarity = isPassive ? "epic" : isBooster ? "rare" : isBookLike ? "epic" : "rare"; const wuxiaName = isBookLike ? `武学残卷·${filename.replace(/^\d+_/u, "").replace(/_/g, " ").replace(/\.png$/iu, "")}` : `秘术符印·${filename.replace(/^\d+_/u, "").replace(/_/g, " ").replace(/\.png$/iu, "")}`; const xianxiaName = isBookLike ? `灵诀玉简·${filename.replace(/^\d+_/u, "").replace(/_/g, " ").replace(/\.png$/iu, "")}` : `神通法印·${filename.replace(/^\d+_/u, "").replace(/_/g, " ").replace(/\.png$/iu, "")}`; const useProfile = category === labels.consumable ? { hpRestore: 0, manaRestore: 24 + rarityToRank(rarity) * 8, cooldownReduction: 1, buildBuffs: buildItemBuildBuffs( `skill-relic-${filename.replace(/\.png$/iu, "").toLowerCase()}`, "功法激发", [role, /Arrow|Spear/u.test(filename) ? "远射" : /Shield/u.test(filename) ? "护体" : "爆发"], 2, ), } : null; const tags = dedupe([ category === labels.consumable ? "consumable" : category === labels.relic ? "relic" : "rare", role, isBooster ? "cooldown" : "", /Fire/u.test(filename) ? "fire" : "", /Lightning/u.test(filename) ? "lightning" : "", /Shield/u.test(filename) ? "ward" : "", /Sword|Punch/u.test(filename) ? "burst" : "", /Arrow|Spear/u.test(filename) ? "projectile" : "", ]); return { name: xianxiaName, category, rarity, tags, description: "技能图标类物品会被设计成功法、符印、强化器或秘卷,用于支撑特定流派的 build 想象。", worldAffinity: "xianxia", equipmentSlotId: category === labels.relic ? "relic" : null, worldProfiles: buildWorldProfiles( wuxiaName, xianxiaName, `${wuxiaName}适合在边城模板里解释为武学秘卷、战术符印或绝招凭证。`, `${xianxiaName}可作为功法玉简、灵术法印或灵能强化器,用于构筑法修流派。`, ), statProfile: category === labels.relic ? buildRelicStats(rarity, role) : null, useProfile, buildProfile: buildBuildProfile(role, tags, { setId: `skill-${role}`, setName: `${role} 功法谱`, synergy: ["职业核心", "技能联动", "法术 build"], }), value: rankValue(rarity, category === labels.relic ? "relic" : null, useProfile), }; } function buildUtilityDesign( normalizedSourcePath: string, labels: ItemCategoryLabels, ): DesignedItemMetadata { const filename = normalizedSourcePath.split("/").pop() ?? normalizedSourcePath; const readable = filename.replace(/^\d+_/u, "").replace(/_/g, " ").replace(/\.png$/iu, ""); const lower = readable.toLowerCase(); const category = /ore|ingot|dust|bone|nail|card|flag|plushie|fish|string|rope|hook|cage|flower|seed|leaf|feather|skin|wood|stone/u.test(lower) ? labels.material : /throwable|snowballs|meat|apple|mushroom/u.test(lower) ? labels.consumable : /rod|pickaxe|sword|bow|mace|shield/u.test(lower) ? /shield/u.test(lower) ? labels.armor : labels.weapon : /book|relic|amulet|charm|skull|eyepatch|hook/u.test(lower) ? labels.relic : labels.rare; const rarity = /gold|angelic|sacred|relic|crystal/u.test(lower) ? "epic" : /steel|silver|special|pirate|magic/u.test(lower) ? "rare" : /iron|bundle|advanced|fresh/u.test(lower) ? "uncommon" : "common"; const slot: EquipmentSlotId | null = category === labels.weapon ? "weapon" : category === labels.armor ? "armor" : category === labels.relic ? "relic" : null; const role = detectRoleFromDescriptor(lower); const useProfile: ItemUseProfile | null = category === labels.consumable ? { hpRestore: /meat|apple|mushroom/u.test(lower) ? 16 + rarityToRank(rarity) * 8 : 0, manaRestore: /magic|crystal|water/u.test(lower) ? 14 + rarityToRank(rarity) * 6 : 0, cooldownReduction: /throwable|snowballs/u.test(lower) ? 1 : 0, } : null; const statProfile = slot === "weapon" ? buildEquipmentStats("weapon", rarity, role, "weapon") : slot === "armor" ? buildEquipmentStats("armor", rarity, role, "armor") : slot === "relic" ? buildRelicStats(rarity, role) : null; const wuxiaName = readable .split(" ") .map((token) => buildGenericTokenName(token, WorldType.WUXIA)) .join(""); const xianxiaName = readable .split(" ") .map((token) => buildGenericTokenName(token, WorldType.XIANXIA)) .join(""); return { name: wuxiaName || readable, category, rarity, tags: dedupe([ ...(/meat|apple|mushroom/u.test(lower) ? ["healing"] : []), ...(/magic|crystal|water/u.test(lower) ? ["mana"] : []), ...(slot === "weapon" ? ["weapon"] : slot === "armor" ? ["armor"] : slot === "relic" ? ["relic"] : []), ...(category === labels.material ? ["material"] : []), role, ]), description: `${readable} 根据视觉和路径被自动归入 ${category} 家族,可作为 ${role} 向 build 的支撑件或素材件。`, worldAffinity: /magic|crystal|sacred|angelic|spirit|astral/u.test(lower) ? "xianxia" : "neutral", equipmentSlotId: slot, worldProfiles: buildWorldProfiles( wuxiaName || readable, xianxiaName || readable, `${wuxiaName || readable}更适合边城模板的在地使用语境。`, `${xianxiaName || readable}更适合灵潮模板的灵物/法器语境。`, ), statProfile, useProfile, buildProfile: buildBuildProfile(role, [category, role], { synergy: ["素材拓展", "过渡 build", "题材补完"], }), value: rankValue(rarity, slot, useProfile), }; } export function buildDesignedItemMetadata( normalizedSourcePath: string, baseName: string, baseCategory: string, baseRarity: ItemRarity, baseTags: string[], labels: ItemCategoryLabels, ): DesignedItemMetadata { const specialized = buildLegacyDesign(normalizedSourcePath, baseName, baseCategory, baseRarity, baseTags, labels) ?? buildArmoryDesign(normalizedSourcePath, labels) ?? buildJewelryDesign(normalizedSourcePath, labels) ?? buildPotionDesign(normalizedSourcePath, labels) ?? buildGemDesign(normalizedSourcePath, labels) ?? buildSkillRelicDesign(normalizedSourcePath, labels); if (specialized) { return { ...specialized, tags: dedupe([...(specialized.tags ?? []), ...baseTags]), }; } const fallback = buildUtilityDesign(normalizedSourcePath, labels); return { ...fallback, name: fallback.name || baseName, category: fallback.category || baseCategory, rarity: fallback.rarity || baseRarity, tags: dedupe([...(fallback.tags ?? []), ...baseTags]), }; }