UPDATE: Skilltree
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m46s

This commit is contained in:
2025-07-30 21:10:09 +07:00
parent 31724fa88c
commit 9b6a061cba
50 changed files with 15044 additions and 6214 deletions

View File

@@ -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/>

View File

@@ -7,6 +7,12 @@
nocompatible: true;
}
:root {
--size-big: 4vw;
--size-medium: 3vw;
--size-small: 2vw;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;

View File

@@ -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>

View File

@@ -0,0 +1,11 @@
"use client";
import SkillsInfo from "@/components/skillsInfo";
export default function SkillsInfoPage() {
return (
<div className="w-full">
<SkillsInfo></SkillsInfo>
</div>
);
}

View File

@@ -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>

View File

@@ -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>
))}

View File

@@ -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>
);

View File

@@ -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">

View File

@@ -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)

View File

@@ -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>

View 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>
);
}

View File

@@ -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);
}

View File

@@ -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: "%"
}
}
}

View 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"],
}
}

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
import { listCurrentLanguage } from "@/lib/constant";
import { listCurrentLanguage } from "@/constant/constant";
import { CharacterBasic, EventBasic, LightConeBasic, MonsterBasic } from "@/types";

View File

@@ -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>);
}

View File

@@ -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' })

View File

@@ -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;
}

View File

@@ -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'

View File

@@ -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])
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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) => {

View File

@@ -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 }),
}),
{

View File

@@ -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 {