UPDATE: Skilltree
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m46s
18711
data/characters.en.json
@@ -212,6 +212,28 @@
|
||||
"changeRelic": "更换遗物",
|
||||
"deleteRelic": "删除遗物",
|
||||
"deleteRelicConfirm": "确定要删除插槽中的遗物吗",
|
||||
"setEffects": "设置效果"
|
||||
"setEffects": "设置效果",
|
||||
"details": "详情",
|
||||
"normal": "普通攻击",
|
||||
"bpskill": "技能",
|
||||
"maze": "技巧",
|
||||
"ultra": "终结技",
|
||||
"servantskill": "记灵技能",
|
||||
"severaltalent": "记灵天赋",
|
||||
"singleattack": "单体攻击",
|
||||
"enhance": "强化",
|
||||
"summon": "召唤",
|
||||
"blast": "爆裂",
|
||||
"restore": "恢复",
|
||||
"support": "支援",
|
||||
"aoeattack": "范围攻击",
|
||||
"mazeattack": "迷宫秘技",
|
||||
"impair": "削弱",
|
||||
"active": "活跃",
|
||||
"inactive": "不活跃",
|
||||
"maxAll": "全部最大化",
|
||||
"defence": "防御",
|
||||
"maxAllSuccess": "技能等级已成功设置为最大。",
|
||||
"maxAllFailed": "设置技能等级为最大失败。"
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,29 @@
|
||||
"changeRelic": "Change relic",
|
||||
"deleteRelic": "Delete relic",
|
||||
"deleteRelicConfirm": "Are you sure you want to delete relic in slot",
|
||||
"setEffects": "Set Effects"
|
||||
"setEffects": "Set Effects",
|
||||
"details": "Details",
|
||||
"normal": "Basic ATK",
|
||||
"bpskill": "Skill",
|
||||
"maze": "Technique",
|
||||
"ultra": "Ultimate",
|
||||
"servantskill": "Memosprite Skill",
|
||||
"severaltalent": "Memosprite Talent",
|
||||
"singleattack": "Single Attack",
|
||||
"enhance": "Enhance",
|
||||
"summon": "Summon",
|
||||
"mazeattack": "Technique Attack",
|
||||
"blast": "Blast",
|
||||
"restore": "Restore",
|
||||
"support": "Support",
|
||||
"aoeattack": "AoE Attack",
|
||||
"impair": "Impair",
|
||||
"bounce": "Bounce",
|
||||
"active": "Active",
|
||||
"defence": "Defence",
|
||||
"inactive": "Inactive",
|
||||
"maxAll": "Max All",
|
||||
"maxAllSuccess": "Successfully set skill level to max.",
|
||||
"maxAllFailed": "Failed to set skill level to max."
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,30 @@
|
||||
"changeRelic": "遺物を変更",
|
||||
"deleteRelic": "遺物を削除",
|
||||
"deleteRelicConfirm": "スロットの遺物を削除してもよろしいですか?",
|
||||
"setEffects": "効果を設定"
|
||||
"setEffects": "効果を設定",
|
||||
"details": "詳細",
|
||||
"normal": "通常攻撃",
|
||||
"bpskill": "スキル",
|
||||
"maze": "技術",
|
||||
"ultra": "アルティメット",
|
||||
"servantskill": "メモスプライトスキル",
|
||||
"severaltalent": "メモスプライトの才能",
|
||||
"singleattack": "単体攻撃",
|
||||
"enhance": "強化",
|
||||
"summon": "召喚",
|
||||
"blast": "爆発",
|
||||
"restore": "回復",
|
||||
"support": "支援",
|
||||
"aoeattack": "範囲攻撃",
|
||||
"mazeattack": "迷宮秘技",
|
||||
"impair": "弱体化",
|
||||
"bounce": "弾跳",
|
||||
"active": "アクティブ",
|
||||
"inactive": "非アクティブ",
|
||||
"defence": "防御",
|
||||
"maxAll": "すべて最大化",
|
||||
"maxAllSuccess": "スキルレベルを最大に設定しました。",
|
||||
"maxAllFailed": "スキルレベルの最大設定に失敗しました。"
|
||||
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,29 @@
|
||||
"changeRelic": "유물 변경",
|
||||
"deleteRelic": "유물 삭제",
|
||||
"deleteRelicConfirm": "이 슬롯의 유물을 삭제하시겠습니까?",
|
||||
"setEffects": "효과 설정"
|
||||
"setEffects": "효과 설정",
|
||||
"details": "상세",
|
||||
"normal": "기본 공격",
|
||||
"bpskill": "스킬",
|
||||
"maze": "전술",
|
||||
"ultra": "필살기",
|
||||
"servantskill": "메모스프라이트 스킬",
|
||||
"severaltalent": "메모스프라이트 재능",
|
||||
"singleattack": "단일 공격",
|
||||
"enhance": "강화",
|
||||
"summon": "소환",
|
||||
"blast": "광역 공격",
|
||||
"restore": "회복",
|
||||
"support": "지원",
|
||||
"aoeattack": "광역 공격",
|
||||
"mazeattack": "미궁 비기",
|
||||
"impair": "약화",
|
||||
"bounce": "튕김",
|
||||
"active": "활성",
|
||||
"inactive": "비활성",
|
||||
"defence": "방어",
|
||||
"maxAll": "모두 최대치",
|
||||
"maxAllSuccess": "스킬 레벨이 최대치로 설정되었습니다.",
|
||||
"maxAllFailed": "스킬 레벨을 최대치로 설정하지 못했습니다."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
"techniqueNote": "Bật hiệu ứng chiến pháp trước trận",
|
||||
"enhancement": "Cường hóa",
|
||||
"enhancementLevel": "Cấp độ cường hóa",
|
||||
"origin": "Nguồn gốc",
|
||||
"origin": "Nguyên gốc",
|
||||
"enhancedNote": "Cường hóa cao mở thêm kỹ năng",
|
||||
"lightconeEquipment": "Trang bị nón ánh sáng",
|
||||
"lightconeSettings": "Cài đặt nón ánh sáng",
|
||||
@@ -212,6 +212,29 @@
|
||||
"changeRelic": "Thay đổi di vật",
|
||||
"deleteRelic": "Xóa di vật",
|
||||
"deleteRelicConfirm": "Bạn có chắc chắn muốn xóa di vật trong ô này không?",
|
||||
"setEffects": "Thiết lập hiệu ứng"
|
||||
}
|
||||
}
|
||||
"setEffects": "Thiết lập hiệu ứng",
|
||||
"details": "Chi tiết",
|
||||
"normal": "Đánh thường",
|
||||
"bpskill": "Chiến kỹ",
|
||||
"maze": "Bí kỹ",
|
||||
"ultra": "Tuyệt kỹ",
|
||||
"servantskill": "Kỹ năng vật triệu hồi",
|
||||
"severaltalent": "Thiên phú vật triệu hồi",
|
||||
"singleattack": "Tấn công đơn",
|
||||
"enhance": "Cường hóa",
|
||||
"summon": "Triệu hồi",
|
||||
"blast": "Tấn công 3 mục tiêu",
|
||||
"restore": "Hồi phục",
|
||||
"support": "Hỗ trợ",
|
||||
"aoeattack": "Tấn công đa mục tiêu",
|
||||
"mazeattack": "Bí kỹ tấn công",
|
||||
"impair": "Suy yếu",
|
||||
"bounce": "Nảy bật",
|
||||
"active": "Kích hoạt",
|
||||
"inactive": "Không kích hoạt",
|
||||
"defence": "Phòng thủ",
|
||||
"maxAll": "Tối đa tất cả",
|
||||
"maxAllSuccess": "Đã thiết lập cấp độ kỹ năng tối đa thành công.",
|
||||
"maxAllFailed": "Thiết lập cấp độ kỹ năng tối đa thất bại."
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,29 @@
|
||||
"changeRelic": "更换遗物",
|
||||
"deleteRelic": "删除遗物",
|
||||
"deleteRelicConfirm": "确定要删除插槽中的遗物吗",
|
||||
"setEffects": "设置效果"
|
||||
"setEffects": "设置效果",
|
||||
"details": "详情",
|
||||
"normal": "普通攻击",
|
||||
"bpskill": "技能",
|
||||
"maze": "技巧",
|
||||
"ultra": "终结技",
|
||||
"servantskill": "记灵技能",
|
||||
"severaltalent": "记灵天赋",
|
||||
"singleattack": "单体攻击",
|
||||
"enhance": "强化",
|
||||
"summon": "召唤",
|
||||
"blast": "爆裂",
|
||||
"restore": "恢复",
|
||||
"support": "支援",
|
||||
"aoeattack": "范围攻击",
|
||||
"mazeattack": "迷宫秘技",
|
||||
"impair": "削弱",
|
||||
"bounce": "弹跳",
|
||||
"active": "活跃",
|
||||
"defence": "防御",
|
||||
"inactive": "不活跃",
|
||||
"maxAll": "全部最大化",
|
||||
"maxAllSuccess": "技能等级已成功设置为最大。",
|
||||
"maxAllFailed": "设置技能等级为最大失败。"
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,16 @@ const nextConfig: NextConfig = {
|
||||
hostname: 'api.hakush.in',
|
||||
pathname: '**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'homdgcat.wiki',
|
||||
pathname: '**',
|
||||
},
|
||||
],
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/api/character/:id',
|
||||
destination: 'https://api.hakush.in/hsr/data/en/character/:id.json',
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default withNextIntl(nextConfig);
|
||||
|
||||
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 914 KiB After Width: | Height: | Size: 846 KiB |
BIN
public/skilltree/KNIGHT.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/skilltree/MAGE.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/skilltree/MEMORY.webp
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
public/skilltree/PRIEST.webp
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
public/skilltree/ROGUE.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/skilltree/SHAMAN.webp
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/skilltree/WARLOCK.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/skilltree/WARRIOR.webp
Normal file
|
After Width: | Height: | Size: 51 KiB |
@@ -1,9 +1,6 @@
|
||||
"use client"
|
||||
import Image from "next/image";
|
||||
import EidolonsInfo from "@/components/eidolonsInfo";
|
||||
import { useRouter } from 'next/navigation'
|
||||
export default function EidolonsInfoPage() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="w-full">
|
||||
<EidolonsInfo/>
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
nocompatible: true;
|
||||
}
|
||||
|
||||
:root {
|
||||
--size-big: 4vw;
|
||||
--size-medium: 3vw;
|
||||
--size-small: 2vw;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use client"
|
||||
import Image from "next/image";
|
||||
"use client";
|
||||
|
||||
import RelicsInfo from "@/components/relicsInfo";
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function RelicsInfoPage() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="w-full">
|
||||
<RelicsInfo></RelicsInfo>
|
||||
|
||||
11
src/app/skills-info/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import SkillsInfo from "@/components/skillsInfo";
|
||||
|
||||
export default function SkillsInfoPage() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<SkillsInfo></SkillsInfo>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ export default function ActionBar() {
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('')}>{transI18n("characterInformation")}</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('relics-info')}>{transI18n("relics")}</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('eidolons-info')}>{transI18n("eidolons")}</button>
|
||||
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("skills")} ({transI18n("comingSoon")})</button>
|
||||
<button className="btn btn-success btn-sm" onClick={() => actionMove('skills-info')}>{transI18n("skills")}</button>
|
||||
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("showcaseCard")} {transI18n("comingSoon")}</button>
|
||||
<button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm"> {isConnectPS ? transI18n("sync") : transI18n("connectPs")}</button>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useFetchASData, useFetchAvatarData, useFetchConfigData, useFetchLightco
|
||||
export default function AvatarBar() {
|
||||
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
|
||||
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
|
||||
const { listAvatar, setAvatarSelected, setFilter, filter } = useAvatarStore()
|
||||
const { listAvatar, setAvatarSelected, setSkillSelected, setFilter, filter } = useAvatarStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore()
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function AvatarBar() {
|
||||
<div className="flex items-start h-full">
|
||||
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 w-full h-[65vh] overflow-y-scroll overflow-x-hidden">
|
||||
{listAvatar.map((item, index) => (
|
||||
<div key={index} onClick={() => setAvatarSelected(item)}>
|
||||
<div key={index} onClick={() => {setAvatarSelected(item); setSkillSelected(null)}}>
|
||||
<CharacterCard data={item} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -21,37 +21,73 @@ const getRarityName = (slot: string) => {
|
||||
switch (slot) {
|
||||
case '1': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/HEAD.png" alt="Head" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/HEAD.png"
|
||||
alt="Head"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Head</h2>
|
||||
</div>
|
||||
);
|
||||
case '2': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/HAND.png" alt="Hand" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/HAND.png"
|
||||
alt="Hand"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Hands</h2>
|
||||
</div>
|
||||
);
|
||||
case '3': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/BODY.png" alt="Body" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/BODY.png"
|
||||
alt="Body"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Body</h2>
|
||||
</div>
|
||||
);
|
||||
case '4': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/FOOT.png" alt="Foot" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/FOOT.png"
|
||||
alt="Foot"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Feet</h2>
|
||||
</div>
|
||||
);
|
||||
case '5': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/NECK.png" alt="Neck" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/NECK.png"
|
||||
alt="Neck"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Planar sphere</h2>
|
||||
</div>
|
||||
);
|
||||
case '6': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/relics/OBJECT.png" alt="Object" width={20} height={20} />
|
||||
<Image
|
||||
src="/relics/OBJECT.png"
|
||||
alt="Object"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Link rope</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { connectToPS, downloadJson, syncDataToPS } from "@/helper";
|
||||
import { converterToFreeSRJson } from "@/helper/converterToFreeSRJson";
|
||||
import { useChangeTheme } from "@/hooks/useChangeTheme";
|
||||
import { listCurrentLanguage } from "@/lib/constant";
|
||||
import { listCurrentLanguage } from "@/constant/constant";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { motion } from "framer-motion";
|
||||
@@ -264,7 +264,7 @@ export default function Header() {
|
||||
{/* Logo */}
|
||||
|
||||
<a className="hidden sm:grid sm:grid-cols-1 items-start justify-items-center text-left gap-0 hover:scale-105 px-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Image src="/ff-srtool.png" alt="Logo" width={50} height={50} />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h1 className="text-xl font-bold">
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function EnkaImport() {
|
||||
}
|
||||
const newProfile = converterOneEnkaDataToAvatarStore(character, newAvatar.profileList.length)
|
||||
if (newProfile) {
|
||||
newAvatar.profileList.push()
|
||||
newAvatar.profileList.push(newProfile)
|
||||
newAvatar.profileSelect = newAvatar.profileList.length - 1
|
||||
}
|
||||
setAvatar(newAvatar)
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useEffect, useMemo } from 'react';
|
||||
import SelectCustomImage from '../select/customSelectImage';
|
||||
import { calcAffixBonus, randomPartition, randomStep, replaceByParam } from '@/helper';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import { mapingStats } from '@/lib/constant';
|
||||
import { mappingStats } from '@/constant/constant';
|
||||
import useAvatarStore from '@/stores/avatarStore';
|
||||
import useModelStore from '@/stores/modelStore';
|
||||
import useRelicMakerStore from '@/stores/relicMakerStore';
|
||||
@@ -118,7 +118,7 @@ export default function RelicMaker() {
|
||||
const data = affixSet[selectedMainStat];
|
||||
if (!data) return 0;
|
||||
|
||||
const stat = mapingStats?.[data.property];
|
||||
const stat = mappingStats?.[data.property];
|
||||
if (!stat) return 0;
|
||||
|
||||
const value = data.base + data.step * selectedRelicLevel;
|
||||
@@ -257,8 +257,8 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(mapMainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={selectedMainStat}
|
||||
@@ -361,13 +361,13 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
|
||||
imageUrl: mapingStats[value.property].icon
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
}))}
|
||||
selectedCustomSet={v.affixId}
|
||||
placeholder={transI18n("selectASubStat")}
|
||||
@@ -378,7 +378,7 @@ export default function RelicMaker() {
|
||||
{/* Current Value */}
|
||||
<div className="col-span-4 text-center flex items-center justify-center gap-2">
|
||||
<span className="text-2xl font-mono">+{ }</span>
|
||||
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
|
||||
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
|
||||
</div>
|
||||
|
||||
{/* Roll Values */}
|
||||
@@ -387,19 +387,19 @@ export default function RelicMaker() {
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
|
||||
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
326
src/components/skillsInfo/index.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
"use client"
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { traceButtonsInfo, traceLink } from "@/constant/traceConstant";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import Image from "next/image";
|
||||
import { replaceByParam } from "@/helper";
|
||||
import { mappingStats } from "@/constant/constant";
|
||||
import { StatusAddType } from "@/types";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function SkillsInfo() {
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { theme } = useLocaleStore()
|
||||
const { avatarSelected, mapAvatarInfo, skillSelected, setSkillSelected } = useAvatarStore()
|
||||
const { avatars, setAvatar } = useUserDataStore()
|
||||
|
||||
const traceButtons = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return traceButtonsInfo[avatarSelected.baseType]
|
||||
}, [avatarSelected])
|
||||
|
||||
const avatarInfo = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return mapAvatarInfo[avatarSelected.id]
|
||||
}, [avatarSelected, mapAvatarInfo])
|
||||
|
||||
|
||||
|
||||
const avatarData = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return avatars[avatarSelected.id]
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarSkillTree = useMemo(() => {
|
||||
if (!avatarSelected || !avatars[avatarSelected.id]) return {}
|
||||
if (avatars[avatarSelected.id].enhanced) {
|
||||
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {}
|
||||
}
|
||||
return avatarInfo?.SkillTrees || {}
|
||||
}, [avatarSelected, avatarInfo, avatars])
|
||||
|
||||
const skillInfo = useMemo(() => {
|
||||
if (!avatarSelected || !skillSelected) return
|
||||
return avatarSkillTree?.[skillSelected || ""]?.["1"]
|
||||
}, [avatarSelected, avatarSkillTree, skillSelected])
|
||||
|
||||
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
|
||||
if (!icon) return
|
||||
if (icon.startsWith("SkillIcon")) {
|
||||
if (Number(avatarSelected?.id) > 8000 && Number(avatarSelected?.id) % 2 === 0) {
|
||||
return `https://homdgcat.wiki/images/skillicons/avatar/${Number(avatarSelected?.id) - 1}/${icon.replaceAll(avatarSelected?.id || "", (Number(avatarSelected?.id) - 1).toString())}`
|
||||
}
|
||||
return `https://homdgcat.wiki/images/skillicons/avatar/${avatarSelected?.id}/${icon}`
|
||||
} else if (status && mappingStats[status.PropertyType]) {
|
||||
return mappingStats[status.PropertyType].icon
|
||||
}
|
||||
else if (icon.startsWith("Icon")) {
|
||||
return `https://api.hakush.in/hsr/UI/trace/${icon.replace(".png", ".webp")}`
|
||||
}
|
||||
}, [avatarSelected])
|
||||
|
||||
const getTraceBuffDisplay = useCallback((status: StatusAddType) => {
|
||||
const dataDisplay = mappingStats[status.PropertyType]
|
||||
if (!dataDisplay) return ""
|
||||
if (dataDisplay.unit === "%") {
|
||||
return `${(status.Value * 100).toFixed(1)}${dataDisplay.unit}`
|
||||
}
|
||||
if (dataDisplay.name === "SPD") {
|
||||
return `${status.Value.toFixed(1)}${dataDisplay.unit}`
|
||||
}
|
||||
return `${status.Value.toFixed(0)}${dataDisplay.unit}`
|
||||
}, [])
|
||||
|
||||
const dataLevelUpSkill = useMemo(() => {
|
||||
const skillIds: number[] = skillInfo?.LevelUpSkillID || []
|
||||
if (!avatarSelected || !avatarInfo || !avatarData) return
|
||||
let result = Object.values(avatarInfo.Skills || {})?.filter((skill) => skillIds.includes(skill.Id))
|
||||
if (avatarData.enhanced) {
|
||||
result = Object.values(avatarInfo.Enhanced[avatarData.enhanced.toString()].Skills || {})?.filter((skill) => skillIds.includes(skill.Id))
|
||||
}
|
||||
if (result && result.length > 0) {
|
||||
return {
|
||||
isServant: false,
|
||||
data: result,
|
||||
servantData: null,
|
||||
}
|
||||
}
|
||||
const resultServant = Object.entries(avatarInfo.Memosprite?.Skills || {})
|
||||
?.filter(([skillId]) => skillIds.includes(Number(skillId)))
|
||||
?.map(([skillId, skillData]) => ({
|
||||
Id: Number(skillId),
|
||||
...skillData,
|
||||
}))
|
||||
if (resultServant && resultServant.length > 0) {
|
||||
return {
|
||||
isServant: true,
|
||||
data: resultServant,
|
||||
servantData: avatarInfo.Memosprite,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarInfo, avatarData])
|
||||
|
||||
|
||||
const handlerMaxAll = () => {
|
||||
if (!avatarInfo || !avatarData || !avatarSkillTree) {
|
||||
toast.error(transI18n("maxAllFailed"))
|
||||
return
|
||||
}
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills = Object.values(avatarSkillTree).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
toast.success(transI18n("maxAllSuccess"))
|
||||
setAvatar(newData)
|
||||
}
|
||||
|
||||
const handlerChangeStatusTrace = (status: boolean) => {
|
||||
if (!avatarData || !skillInfo) return
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
|
||||
|
||||
if (!status && traceLink?.[avatarSelected?.baseType || ""]?.[skillSelected || ""]) {
|
||||
traceLink[avatarSelected?.baseType || ""][skillSelected || ""].forEach((pointId) => {
|
||||
if (avatarSkillTree?.[pointId]?.["1"]) {
|
||||
console.log(avatarSkillTree?.[pointId]?.["1"].PointID)
|
||||
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
setAvatar(newData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div className="rounded-xl p-6 shadow-lg">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
|
||||
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("skills")}
|
||||
</h2>
|
||||
<div className="flex flex-col items-center">
|
||||
<button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button>
|
||||
{traceButtons && avatarInfo && (
|
||||
<div className="grid col-span-4 relative w-full aspect-square">
|
||||
<Image
|
||||
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`}
|
||||
alt=""
|
||||
width={612}
|
||||
priority={true}
|
||||
height={612}
|
||||
style={{
|
||||
filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none"
|
||||
}}
|
||||
className={`w-full h-full object-cover rounded-xl`}
|
||||
/>
|
||||
{traceButtons.map((btn, index) => (
|
||||
<div
|
||||
key={`${btn.id} + ${index}`}
|
||||
id={btn.id}
|
||||
className={`
|
||||
absolute rounded-full border border-black
|
||||
bg-no-repeat bg-contain
|
||||
cursor-pointer transition-all duration-200 ease-in-out
|
||||
shadow-[0_0_5px_white] flex justify-center items-center
|
||||
hover:scale-110
|
||||
${btn.size === "small" ? "w-[2vw] h-[2vw] bg-white" : ""}
|
||||
${btn.size === "medium" ? "w-[3vw] h-[3vw] bg-white" : ""}
|
||||
${btn.size === "big" ? "w-[3.5vw] h-[3.5vw] bg-black" : ""}
|
||||
${skillSelected === btn.id ? "border-4 border-primary" : ""}
|
||||
${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] === 0 ? "opacity-50 cursor-not-allowed" : ""}
|
||||
`}
|
||||
onClick={() => {
|
||||
setSkillSelected(btn.id === skillSelected ? null : btn.id)
|
||||
}}
|
||||
style={{
|
||||
left: `calc(${btn.left} - var(--size-${btn.size}) / 2)`,
|
||||
top: `calc(${btn.top} - var(--size-${btn.size}) / 2)`,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={getImageSkill(avatarInfo?.SkillTrees?.[btn.id]?.["1"]?.Icon, avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]) || ""}
|
||||
alt={btn.id.replaceAll("Point", "")}
|
||||
priority={true}
|
||||
width={124}
|
||||
height={124}
|
||||
style={{
|
||||
filter: btn.size !== "big"
|
||||
? 'brightness(0%)'
|
||||
: 'brightness(200%)'
|
||||
}}
|
||||
/>
|
||||
{btn.size === "big" && (
|
||||
<p className="text-xs md:text-base font-bold text-center rounded-full absolute bottom-[-1.4vw] left-1/2 transform -translate-x-1/2">{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
|
||||
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("details")}
|
||||
</h2>
|
||||
{skillSelected && avatarInfo?.SkillTrees && avatarData && (
|
||||
<div>
|
||||
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
|
||||
<div>
|
||||
<div className="font-bold text-success">{transI18n("level")}</div>
|
||||
<div className="w-full max-w-xs">
|
||||
<input type="range"
|
||||
min={1}
|
||||
max={skillInfo?.MaxLevel || 1}
|
||||
value={avatarData?.data.skills?.[skillInfo?.PointID] || 1}
|
||||
onChange={(e) => {
|
||||
const newData = cloneDeep(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = parseInt(e.target.value)
|
||||
setAvatar(newData)
|
||||
}}
|
||||
className="range range-success"
|
||||
step="1" />
|
||||
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
||||
{Array.from({ length: skillInfo?.MaxLevel }, (_, index) => index + 1).map((index) => (
|
||||
<span key={index}>{index}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillSelected)?.size !== "big" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1}
|
||||
className="toggle toggle-success"
|
||||
onChange={(e) => {
|
||||
handlerChangeStatusTrace(e.target.checked)
|
||||
}}
|
||||
/>
|
||||
<div className="font-bold text-success">
|
||||
{avatarData?.data.skills?.[skillInfo?.PointID] === 1 ? transI18n("active") : transI18n("inactive")}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
|
||||
{((skillInfo?.PointName && skillInfo?.PointDesc) ||
|
||||
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
|
||||
&& (
|
||||
<div className="text-xl font-bold flex items-center gap-2 mt-2">
|
||||
{skillInfo.PointName}
|
||||
{skillInfo.StatusAddList.length > 0 && (
|
||||
<div>
|
||||
{skillInfo.StatusAddList.map((status, index) => (
|
||||
<div key={index}>
|
||||
<div className="text-xl font-bold">{getTraceBuffDisplay(status)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{skillInfo?.PointDesc && (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skillInfo?.PointDesc || "",
|
||||
skillInfo?.ParamList || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{skillInfo?.LevelUpSkillID
|
||||
&& skillInfo?.LevelUpSkillID.length > 0
|
||||
&& dataLevelUpSkill
|
||||
&& (
|
||||
<div className="mt-2 flex flex-col gap-2">
|
||||
|
||||
{dataLevelUpSkill?.data?.map((skill, index) => (
|
||||
<div key={index}>
|
||||
|
||||
<div className="text-xl font-bold text-primary">
|
||||
{transI18n(dataLevelUpSkill.isServant ? `${skill?.Type ? "severaltalent" : "servantskill"}` : `${skill?.Type ? skill?.Type.toLowerCase() : "talent"}`)}
|
||||
{` (${transI18n(skill.Tag.toLowerCase())})`}
|
||||
</div>
|
||||
|
||||
<div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(skill.Name, []) }}>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skill.Desc || skill.SimpleDesc,
|
||||
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.ParamList || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { createContext, PropsWithChildren, useEffect, useState } from "react";
|
||||
import { createContext, PropsWithChildren, useEffect } from "react";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
|
||||
interface ThemeContextType {
|
||||
theme?: string;
|
||||
@@ -8,7 +9,8 @@ interface ThemeContextType {
|
||||
export const ThemeContext = createContext<ThemeContextType>({});
|
||||
|
||||
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||
const [theme, setTheme] = useState<string>("light");
|
||||
|
||||
const { theme, setTheme } = useLocaleStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
@@ -19,14 +21,13 @@ export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const changeTheme = (nextTheme: string | null) => {
|
||||
console.log(nextTheme)
|
||||
if (nextTheme) {
|
||||
setTheme(nextTheme);
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("theme", nextTheme);
|
||||
}
|
||||
} else {
|
||||
setTheme((prev) => (prev === "light" ? "night" : "light"));
|
||||
setTheme(theme === "winter" ? "night" : "winter");
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const listCurrentLanguageApi : Record<string, string> = {
|
||||
zh: "cn"
|
||||
};
|
||||
|
||||
export const mapingStats = <Record<string, {name: string, icon: string, unit: string}> > {
|
||||
export const mappingStats = <Record<string, {name: string, icon: string, unit: string}> > {
|
||||
"HPDelta": {
|
||||
name:"HP",
|
||||
icon:"/icon/hp.webp",
|
||||
@@ -125,4 +125,6 @@ export const mapingStats = <Record<string, {name: string, icon: string, unit: st
|
||||
icon:"/icon/energy-rate.webp",
|
||||
unit: "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
238
src/constant/traceConstant.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
|
||||
|
||||
export const traceButtonsInfo: Record<string, { id: string, size: string, left: string, top: string }[]> = {
|
||||
Knight: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '52%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '35%' },
|
||||
{ id: 'Point02', size: 'big', left: '67%', top: '55%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '69.5%' },
|
||||
{ id: 'Point01', size: 'big', left: '33%', top: '55%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '21.5%' },
|
||||
{ id: 'Point07', size: 'medium', left: '71%', top: '86%' },
|
||||
{ id: 'Point06', size: 'medium', left: '29%', top: '86%' },
|
||||
{ id: 'Point16', size: 'small', left: '50%', top: '9%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '13%' },
|
||||
{ id: 'Point17', size: 'small', left: '34%', top: '13%' },
|
||||
{ id: 'Point15', size: 'small', left: '79.5%', top: '44.5%' },
|
||||
{ id: 'Point12', size: 'small', left: '20.5%', top: '44.5%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '82.5%' },
|
||||
{ id: 'Point13', size: 'small', left: '81%', top: '78%' },
|
||||
{ id: 'Point10', size: 'small', left: '19.5%', top: '78.5%' },
|
||||
{ id: 'Point14', size: 'small', left: '89%', top: '70%' },
|
||||
{ id: 'Point11', size: 'small', left: '11%', top: '70%' }
|
||||
],
|
||||
Mage: [
|
||||
{ id: 'Point03', size: 'big', left: '50.5%', top: '53.5%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '32%' },
|
||||
{ id: 'Point02', size: 'big', left: '68.5%', top: '53.5%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '84%' },
|
||||
{ id: 'Point01', size: 'big', left: '33.5%', top: '53.5%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '14.5%' },
|
||||
{ id: 'Point07', size: 'medium', left: '80.5%', top: '53%' },
|
||||
{ id: 'Point06', size: 'medium', left: '19.5%', top: '53%' },
|
||||
{ id: 'Point17', size: 'small', left: '66%', top: '17.5%' },
|
||||
{ id: 'Point16', size: 'small', left: '34%', top: '17.5%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '79%' },
|
||||
{ id: 'Point09', size: 'small', left: '34%', top: '79%' },
|
||||
{ id: 'Point13', size: 'small', left: '93%', top: '52%' },
|
||||
{ id: 'Point14', size: 'small', left: '89%', top: '63%' },
|
||||
{ id: 'Point15', size: 'small', left: '89%', top: '41.5%' },
|
||||
{ id: 'Point10', size: 'small', left: '7%', top: '52%' },
|
||||
{ id: 'Point11', size: 'small', left: '11.5%', top: '63%' },
|
||||
{ id: 'Point12', size: 'small', left: '12%', top: '41.5%' }
|
||||
],
|
||||
Priest: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '49%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '31%' },
|
||||
{ id: 'Point02', size: 'big', left: '68.5%', top: '46%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '66%' },
|
||||
{ id: 'Point01', size: 'big', left: '33.5%', top: '46%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '13%' },
|
||||
{ id: 'Point07', size: 'medium', left: '68%', top: '79%' },
|
||||
{ id: 'Point06', size: 'medium', left: '33%', top: '79%' },
|
||||
{ id: 'Point16', size: 'small', left: '65%', top: '18%' },
|
||||
{ id: 'Point17', size: 'small', left: '35%', top: '18%' },
|
||||
{ id: 'Point09', size: 'small', left: '57%', top: '89%' },
|
||||
{ id: 'Point18', size: 'small', left: '43%', top: '89%' },
|
||||
{ id: 'Point10', size: 'small', left: '80.5%', top: '66%' },
|
||||
{ id: 'Point11', size: 'small', left: '93%', top: '52%' },
|
||||
{ id: 'Point12', size: 'small', left: '81%', top: '39%' },
|
||||
{ id: 'Point13', size: 'small', left: '20.5%', top: '66%' },
|
||||
{ id: 'Point14', size: 'small', left: '7%', top: '52%' },
|
||||
{ id: 'Point15', size: 'small', left: '20%', top: '39%' }
|
||||
],
|
||||
Rogue: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '53%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '36%' },
|
||||
{ id: 'Point02', size: 'big', left: '68.5%', top: '46%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '70%' },
|
||||
{ id: 'Point01', size: 'big', left: '33.5%', top: '46%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '22%' },
|
||||
{ id: 'Point07', size: 'medium', left: '68%', top: '71%' },
|
||||
{ id: 'Point06', size: 'medium', left: '32%', top: '71%' },
|
||||
{ id: 'Point16', size: 'small', left: '50%', top: '9%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '14%' },
|
||||
{ id: 'Point17', size: 'small', left: '34%', top: '14%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '87%' },
|
||||
{ id: 'Point15', size: 'small', left: '81%', top: '35%' },
|
||||
{ id: 'Point12', size: 'small', left: '19%', top: '35%' },
|
||||
{ id: 'Point13', size: 'small', left: '81%', top: '57%' },
|
||||
{ id: 'Point10', size: 'small', left: '20%', top: '57%' },
|
||||
{ id: 'Point14', size: 'small', left: '93%', top: '44%' },
|
||||
{ id: 'Point11', size: 'small', left: '7%', top: '44%' }
|
||||
],
|
||||
Shaman: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '57%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '36%' },
|
||||
{ id: 'Point02', size: 'big', left: '68.5%', top: '41%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '75%' },
|
||||
{ id: 'Point01', size: 'big', left: '33.5%', top: '41%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '22%' },
|
||||
{ id: 'Point07', size: 'medium', left: '89.5%', top: '56.5%' },
|
||||
{ id: 'Point06', size: 'medium', left: '17.5%', top: '56.5%' },
|
||||
{ id: 'Point16', size: 'small', left: '50%', top: '10%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '14%' },
|
||||
{ id: 'Point17', size: 'small', left: '34%', top: '14%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '87%' },
|
||||
{ id: 'Point15', size: 'small', left: '62%', top: '83%' },
|
||||
{ id: 'Point12', size: 'small', left: '38%', top: '83%' },
|
||||
{ id: 'Point13', size: 'small', left: '77%', top: '70%' },
|
||||
{ id: 'Point14', size: 'small', left: '67%', top: '61%' },
|
||||
{ id: 'Point10', size: 'small', left: '7%', top: '44%' },
|
||||
{ id: 'Point11', size: 'small', left: '20%', top: '31%' }
|
||||
],
|
||||
Warlock: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '44%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '25%' },
|
||||
{ id: 'Point02', size: 'big', left: '68.5%', top: '47%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '62%' },
|
||||
{ id: 'Point01', size: 'big', left: '33.5%', top: '47%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '8%' },
|
||||
{ id: 'Point07', size: 'medium', left: '81.5%', top: '37%' },
|
||||
{ id: 'Point06', size: 'medium', left: '20.5%', top: '37%' },
|
||||
{ id: 'Point17', size: 'small', left: '66%', top: '14%' },
|
||||
{ id: 'Point16', size: 'small', left: '34%', top: '14%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '74%' },
|
||||
{ id: 'Point18', size: 'small', left: '50%', top: '87%' },
|
||||
{ id: 'Point13', size: 'small', left: '94%', top: '48%' },
|
||||
{ id: 'Point14', size: 'small', left: '81%', top: '61%' },
|
||||
{ id: 'Point15', size: 'small', left: '68%', top: '74%' },
|
||||
{ id: 'Point10', size: 'small', left: '6%', top: '48%' },
|
||||
{ id: 'Point11', size: 'small', left: '20%', top: '61%' },
|
||||
{ id: 'Point12', size: 'small', left: '33%', top: '74%' }
|
||||
],
|
||||
Warrior: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '53%' },
|
||||
{ id: 'Point04', size: 'big', left: '51%', top: '36%' },
|
||||
{ id: 'Point02', size: 'big', left: '69%', top: '48%' },
|
||||
{ id: 'Point05', size: 'big', left: '51%', top: '70.5%' },
|
||||
{ id: 'Point01', size: 'big', left: '33%', top: '48%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '22%' },
|
||||
{ id: 'Point07', size: 'medium', left: '67%', top: '83%' },
|
||||
{ id: 'Point06', size: 'medium', left: '33%', top: '83%' },
|
||||
{ id: 'Point16', size: 'small', left: '50%', top: '9%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '14%' },
|
||||
{ id: 'Point17', size: 'small', left: '34%', top: '14%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '87%' },
|
||||
{ id: 'Point15', size: 'small', left: '81%', top: '43.5%' },
|
||||
{ id: 'Point12', size: 'small', left: '19%', top: '43.5%' },
|
||||
{ id: 'Point13', size: 'small', left: '81%', top: '70%' },
|
||||
{ id: 'Point10', size: 'small', left: '19%', top: '70%' },
|
||||
{ id: 'Point14', size: 'small', left: '93%', top: '56.5%' },
|
||||
{ id: 'Point11', size: 'small', left: '7%', top: '56.5%' }
|
||||
],
|
||||
Memory: [
|
||||
{ id: 'Point03', size: 'big', left: '51%', top: '72%' },
|
||||
{ id: 'Point04', size: 'big', left: '75%', top: '53%' },
|
||||
{ id: 'Point02', size: 'big', left: '67%', top: '67%' },
|
||||
{ id: 'Point05', size: 'big', left: '27%', top: '53%' },
|
||||
{ id: 'Point01', size: 'big', left: '35%', top: '67%' },
|
||||
{ id: 'Point08', size: 'medium', left: '34%', top: '34%' },
|
||||
{ id: 'Point07', size: 'medium', left: '50%', top: '87%' },
|
||||
{ id: 'Point06', size: 'medium', left: '91%', top: '51%' },
|
||||
{ id: 'Point16', size: 'small', left: '27.5%', top: '22%' },
|
||||
{ id: 'Point17', size: 'small', left: '43%', top: '14%' },
|
||||
{ id: 'Point18', size: 'small', left: '59%', top: '14%' },
|
||||
{ id: 'Point19', size: 'big', left: '51%', top: '50%' },
|
||||
{ id: 'Point20', size: 'big', left: '51%', top: '28%' },
|
||||
{ id: 'Point12', size: 'small', left: '86%', top: '40%' },
|
||||
{ id: 'Point13', size: 'small', left: '86%', top: '63%' },
|
||||
{ id: 'Point14', size: 'small', left: '35%', top: '82%' },
|
||||
{ id: 'Point15', size: 'small', left: '65%', top: '82%' },
|
||||
{ id: 'Point09', size: 'small', left: '9%', top: '51%' },
|
||||
{ id: 'Point10', size: 'small', left: '13%', top: '40%' },
|
||||
{ id: 'Point11', size: 'small', left: '13%', top: '63%' }
|
||||
]
|
||||
}
|
||||
|
||||
export const traceLink : Record<string, Record<string, string[]>> = {
|
||||
Knight: {
|
||||
Point06: ["Point10", "Point11"],
|
||||
Point07: ["Point13", "Point14"],
|
||||
Point08: ["Point16", "Point17", "Point18"],
|
||||
Point09: ["Point07", "Point06", "Point10", "Point11", "Point13", "Point14"],
|
||||
Point10: ["Point11"],
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point13: ["Point14"],
|
||||
},
|
||||
Mage: {
|
||||
Point08: ["Point16", "Point17"],
|
||||
Point07: ["Point13", "Point14", "Point15"],
|
||||
Point13: ["Point14", "Point15"],
|
||||
Point06: ["Point10", "Point11", "Point12"],
|
||||
Point10: ["Point11", "Point12"],
|
||||
},
|
||||
Priest: {
|
||||
Point08: ["Point16", "Point17"],
|
||||
Point07: ["Point10", "Point11", "Point12"],
|
||||
Point10: ["Point11", "Point12"],
|
||||
Point11: ["Point12"],
|
||||
Point06: ["Point13", "Point14", "Point15"],
|
||||
Point13: ["Point14", "Point15"],
|
||||
Point14: ["Point15"],
|
||||
},
|
||||
Rogue: {
|
||||
Point08: ["Point16", "Point17", "Point18"],
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point07: ["Point13", "Point14"],
|
||||
Point13: ["Point14"],
|
||||
Point06: ["Point10", "Point11"],
|
||||
Point10: ["Point11"],
|
||||
},
|
||||
Shaman: {
|
||||
Point08: ["Point16", "Point17", "Point18"],
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point07: ["Point13", "Point14"],
|
||||
Point13: ["Point14"],
|
||||
Point06: ["Point10", "Point11"],
|
||||
Point10: ["Point11"],
|
||||
Point09: ["Point15", "Point12"],
|
||||
},
|
||||
Warlock: {
|
||||
Point08: ["Point16", "Point17"],
|
||||
Point07: ["Point13", "Point14", "Point15"],
|
||||
Point13: ["Point14", "Point15"],
|
||||
Point14: ["Point15"],
|
||||
Point06: ["Point10", "Point11", "Point12"],
|
||||
Point10: ["Point11", "Point12"],
|
||||
Point11: ["Point12"],
|
||||
Point09: ["Point18"],
|
||||
},
|
||||
Warrior: {
|
||||
Point08: ["Point16", "Point17", "Point18"],
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point07: ["Point13", "Point14", "Point15"],
|
||||
Point13: ["Point14", "Point15"],
|
||||
Point14: ["Point15"],
|
||||
Point06: ["Point10", "Point11", "Point12"],
|
||||
Point10: ["Point11", "Point12"],
|
||||
Point11: ["Point12"],
|
||||
},
|
||||
Memory: {
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point17: ["Point18"],
|
||||
Point09: ["Point10", "Point11"],
|
||||
Point07: ["Point14", "Point15"],
|
||||
Point06: ["Point12", "Point13"],
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mapingStats } from "@/lib/constant"
|
||||
import { mappingStats } from "@/constant/constant"
|
||||
import { AffixDetail } from "@/types"
|
||||
|
||||
export function calcPromotion(level: number) {
|
||||
@@ -40,10 +40,10 @@ export function calcRarity(rarity: string) {
|
||||
export const calcAffixBonus = (affix: AffixDetail, stepCount: number, rollCount: number) => {
|
||||
const data = affix;
|
||||
if (!data) return 0;
|
||||
if (mapingStats?.[data.property].unit === "%") {
|
||||
if (mappingStats?.[data.property].unit === "%") {
|
||||
return ((data.base * rollCount + data.step * stepCount) * 100).toFixed(1);
|
||||
}
|
||||
if (mapingStats?.[data.property].name === "SPD") {
|
||||
if (mappingStats?.[data.property].name === "SPD") {
|
||||
return (data.base * rollCount + data.step * stepCount).toFixed(1);
|
||||
}
|
||||
return (data.base * rollCount + data.step * stepCount).toFixed(0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { listCurrentLanguage } from "@/lib/constant";
|
||||
import { listCurrentLanguage } from "@/constant/constant";
|
||||
import { CharacterBasic, EventBasic, LightConeBasic, MonsterBasic } from "@/types";
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
|
||||
export function getSkillTree(enhanced: string) {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore.getState()
|
||||
@@ -20,3 +20,4 @@ export function getSkillTree(enhanced: string) {
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function downloadJson(fileName: string, data: any) {
|
||||
const json = JSON.stringify(data, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
|
||||
@@ -1,26 +1,52 @@
|
||||
export function replaceByParam(desc: string, params: number[]): string {
|
||||
desc = desc.replace(/<color=#[0-9a-fA-F]{8}>(.*?)<\/color>/g, (match, inner) => {
|
||||
const colorCode = match.match(/#[0-9a-fA-F]{8}/)?.[0] ?? "#ffffff";
|
||||
const processed = inner.replace(/#(\d+)\[i\](%)?/g, (_: string, index: string, percent: string | undefined) => {
|
||||
const i = parseInt(index, 10) - 1;
|
||||
const value = params[i];
|
||||
if (value === undefined) return "";
|
||||
return percent ? `${(value * 100).toFixed(0)}%` : `${value}`;
|
||||
}).replace(/<unbreak>(.*?)<\/unbreak>/g, "$1");
|
||||
function formatParam(
|
||||
indexStr: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string {
|
||||
const i: number = parseInt(indexStr, 10) - 1;
|
||||
const value: number | undefined = params[i];
|
||||
if (value === undefined) return "";
|
||||
|
||||
if (format.startsWith("f")) {
|
||||
const digits: number = parseInt(floatDigits || "1", 10);
|
||||
const num: number = percent ? value * 100 : value;
|
||||
return `${num.toFixed(digits)}${percent ? "%" : ""}`;
|
||||
}
|
||||
|
||||
if (format === "i") {
|
||||
return percent ? `${(value * 100).toFixed(0)}%` : `${Math.round(value)}`;
|
||||
}
|
||||
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
const desc1 = desc.replace(/<color=#[0-9a-fA-F]{8}>(.*?)<\/color>/g, (match: string, inner: string): string => {
|
||||
const colorCode: string = match.match(/#[0-9a-fA-F]{8}/)?.[0] ?? "#ffffff";
|
||||
const processed: string = inner
|
||||
.replace(/#(\d+)\[(f(\d+)|i)\](%)?/g, (
|
||||
_: string,
|
||||
index: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string => formatParam(index, format, floatDigits, percent))
|
||||
.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1");
|
||||
|
||||
return `<span style="color:${colorCode}">${processed}</span>`;
|
||||
});
|
||||
|
||||
desc = desc.replace(/<unbreak>#(\d+)\[i\](%)?<\/unbreak>/g, (_: string, index: string, percent: string | undefined) => {
|
||||
const i = parseInt(index, 10) - 1;
|
||||
const value = params[i];
|
||||
if (value === undefined) return "";
|
||||
return percent ? `${(value * 100).toFixed(0)}%` : `${value}`;
|
||||
});
|
||||
const desc2 = desc1.replace(/<unbreak>#(\d+)\[(f(\d+)|i)\](%)?<\/unbreak>/g, (
|
||||
_: string,
|
||||
index: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string => formatParam(index, format, floatDigits, percent));
|
||||
|
||||
desc = desc.replace(/<unbreak>(\d+)<\/unbreak>/g, (_, number) => number);
|
||||
const desc3 = desc2.replace(/<unbreak>(\d+)<\/unbreak>/g, (_: string, number: string): string => number);
|
||||
|
||||
desc = desc.replaceAll("\\n", "<br></br>");
|
||||
|
||||
return desc;
|
||||
const desc4 = desc3.replaceAll("\\n", "<br></br>");
|
||||
return desc4;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchASByIdsNative, getASEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fetchCharactersByIdsNative, getCharacterListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import useAvatarStore from '@/stores/avatarStore'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useUserDataStore from '@/stores/userDataStore'
|
||||
import { converterToAvatarStore } from '@/helper'
|
||||
@@ -12,7 +12,7 @@ import { CharacterDetail } from '@/types'
|
||||
|
||||
export const useFetchAvatarData = () => {
|
||||
const { setAvatars, avatars } = useUserDataStore()
|
||||
const { setListAvatar, setAllMapAvatarInfo, mapAvatarInfo, setAvatarSelected } = useAvatarStore()
|
||||
const { setListAvatar, setAllMapAvatarInfo, mapAvatarInfo, setAvatarSelected, avatarSelected } = useAvatarStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataAvatar, error: errorAvatar } = useQuery({
|
||||
queryKey: ['avatarData'],
|
||||
@@ -57,12 +57,15 @@ export const useFetchAvatarData = () => {
|
||||
useEffect(() => {
|
||||
if (dataAvatar && !errorAvatar) {
|
||||
setListAvatar(dataAvatar)
|
||||
setAvatarSelected(dataAvatar[0])
|
||||
if (!avatarSelected) {
|
||||
setAvatarSelected(dataAvatar[0])
|
||||
}
|
||||
|
||||
} else if (errorAvatar) {
|
||||
toast.error("Failed to load avatar data")
|
||||
}
|
||||
}, [dataAvatar, errorAvatar, setAvatarSelected, setAvatars, setListAvatar, avatars])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataAvatar, errorAvatar])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataAvatarInfo && !errorAvatarInfo) {
|
||||
@@ -70,5 +73,6 @@ export const useFetchAvatarData = () => {
|
||||
} else if (errorAvatarInfo) {
|
||||
toast.error("Failed to load avatar info data")
|
||||
}
|
||||
}, [dataAvatarInfo, errorAvatarInfo, setAllMapAvatarInfo, setAvatars])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataAvatarInfo, errorAvatarInfo])
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchLightconesByIdsNative, getLightconeListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import useLightconeStore from '@/stores/lightconeStore'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchMOCByIdsNative, getMOCEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchPFByIdsNative, getPFEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchRelicsByIdsNative, getRelicSetListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import useRelicStore from '@/stores/relicStore'
|
||||
import { listCurrentLanguageApi } from '@/lib/constant'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
|
||||
@@ -8,11 +8,13 @@ interface AvatarState {
|
||||
filter: FilterAvatarType;
|
||||
avatarSelected: CharacterBasic | null;
|
||||
mapAvatarInfo: Record<string, CharacterDetail>;
|
||||
skillSelected: string | null;
|
||||
setListAvatar: (newListAvatar: CharacterBasic[]) => void;
|
||||
setAvatarSelected: (newAvatarSelected: CharacterBasic) => void;
|
||||
setFilter: (newFilter: FilterAvatarType) => void;
|
||||
setMapAvatarInfo: (avatarId: string, newCharacter: CharacterDetail) => void;
|
||||
setAllMapAvatarInfo: (newCharacter: Record<string, CharacterDetail>) => void;
|
||||
setSkillSelected: (newSkillSelected: string | null) => void;
|
||||
}
|
||||
|
||||
const useAvatarStore = create<AvatarState>((set, get) => ({
|
||||
@@ -26,7 +28,9 @@ const useAvatarStore = create<AvatarState>((set, get) => ({
|
||||
locale: "",
|
||||
},
|
||||
avatarSelected: null,
|
||||
skillSelected: null,
|
||||
mapAvatarInfo: {},
|
||||
setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }),
|
||||
setListAvatar: (newListAvatar: CharacterBasic[]) => set({ listAvatar: newListAvatar, listRawAvatar: newListAvatar }),
|
||||
setAvatarSelected: (newAvatarSelected: CharacterBasic) => set({ avatarSelected: newAvatarSelected }),
|
||||
setFilter: (newFilter: FilterAvatarType) => {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
|
||||
interface LocaleState {
|
||||
locale: string;
|
||||
theme: string;
|
||||
setTheme: (newTheme: string) => void;
|
||||
setLocale: (newLocale: string) => void;
|
||||
}
|
||||
|
||||
@@ -11,6 +13,8 @@ const useLocaleStore = create<LocaleState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
locale: "en",
|
||||
theme: "night",
|
||||
setTheme: (newTheme: string) => set({ theme: newTheme }),
|
||||
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export interface CharacterDetail {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
@@ -50,7 +51,8 @@ export interface RankType {
|
||||
export interface SkillType {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Desc: string | null;
|
||||
SimpleDesc: string;
|
||||
Type: string;
|
||||
Tag: string;
|
||||
SPBase: number | null;
|
||||
@@ -66,6 +68,13 @@ export interface LevelParams {
|
||||
ParamList: number[];
|
||||
}
|
||||
|
||||
export type StatusAddType = {
|
||||
$type: string;
|
||||
PropertyType: string;
|
||||
Value: number;
|
||||
Name: string;
|
||||
};
|
||||
|
||||
export interface SkillTreePoint {
|
||||
Anchor: string;
|
||||
AvatarPromotionLimit: number | null;
|
||||
@@ -75,14 +84,14 @@ export interface SkillTreePoint {
|
||||
LevelUpSkillID: number[];
|
||||
MaterialList: ItemConfigRow[];
|
||||
MaxLevel: number;
|
||||
ParamList: any[];
|
||||
ParamList: number[];
|
||||
PointID: number;
|
||||
PointName: string | null;
|
||||
PointDesc: string | null;
|
||||
PointTriggerKey: number;
|
||||
PointType: number;
|
||||
PrePoint: string[];
|
||||
StatusAddList: any[];
|
||||
StatusAddList: StatusAddType[];
|
||||
}
|
||||
|
||||
export interface ItemConfigRow {
|
||||
@@ -109,6 +118,7 @@ export interface Memosprite {
|
||||
export interface SpriteSkill {
|
||||
Name: string;
|
||||
Desc: string | null;
|
||||
SimpleDesc: string;
|
||||
Type: string | null;
|
||||
Tag: string;
|
||||
SPBase: number | null;
|
||||
@@ -116,6 +126,7 @@ export interface SpriteSkill {
|
||||
BPAdd: number | null;
|
||||
ShowStanceList: number[];
|
||||
SkillComboValueDelta: number | null;
|
||||
Extra: Record<string, Extra>;
|
||||
Level: Record<string, LevelParams>;
|
||||
}
|
||||
|
||||
@@ -124,6 +135,13 @@ export interface UniqueAbility {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Param: number[];
|
||||
Extra: Record<string, Extra>;
|
||||
}
|
||||
|
||||
export interface Extra {
|
||||
name: string;
|
||||
desc: string;
|
||||
param: number[];
|
||||
}
|
||||
|
||||
export interface Stat {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
|
||||