UPDATE: New monster data
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m20s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m20s
This commit is contained in:
25
src/app/api/data/[name]/route.ts
Normal file
25
src/app/api/data/[name]/route.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getDataCache } from "@/lib/cache/cache"
|
||||
|
||||
export async function GET(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ name: string }> }
|
||||
) {
|
||||
const { name } = await params
|
||||
|
||||
const item = getDataCache(name)
|
||||
|
||||
if (!item) {
|
||||
return new Response("Not found", { status: 404 })
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "public, max-age=3600"
|
||||
}
|
||||
|
||||
if (item.type === "br") {
|
||||
headers["Content-Encoding"] = "br"
|
||||
}
|
||||
|
||||
return new Response(new Uint8Array(item.buf), { headers })
|
||||
}
|
||||
@@ -73,22 +73,22 @@ export default async function RootLayout({
|
||||
<QueryProviderWrapper>
|
||||
<ThemeProvider>
|
||||
<ClientThemeWrapper>
|
||||
<ClientDataFetcher />
|
||||
<div className="min-h-screen w-full">
|
||||
<Header />
|
||||
<div className="grid grid-cols-12 w-full">
|
||||
<div className="hidden sm:block md:col-span-4 lg:col-span-3 sticky top-0 self-start h-fit">
|
||||
<AvatarBar />
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-8 lg:col-span-9">
|
||||
<ActionBar />
|
||||
{children}
|
||||
<ClientDataFetcher >
|
||||
<div className="min-h-screen w-full">
|
||||
<Header />
|
||||
<div className="grid grid-cols-12 w-full">
|
||||
<div className="hidden sm:block md:col-span-4 lg:col-span-3 sticky top-0 self-start h-fit">
|
||||
<AvatarBar />
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-8 lg:col-span-9">
|
||||
<ActionBar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
</ClientDataFetcher>
|
||||
</ClientThemeWrapper>
|
||||
</ThemeProvider>
|
||||
</QueryProviderWrapper>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
|
||||
import useListAvatarStore from "@/stores/avatarStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from 'next/navigation'
|
||||
@@ -19,12 +18,14 @@ import { connectToPS, syncDataToPS } from "@/helper";
|
||||
import CopyImport from "../importBar/copy";
|
||||
import useCopyProfileStore from "@/stores/copyProfile";
|
||||
import AvatarBar from "../avatarBar";
|
||||
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function ActionBar() {
|
||||
const router = useRouter()
|
||||
const { avatarSelected, listRawAvatar } = useListAvatarStore()
|
||||
const { setListCopyAvatar } = useCopyProfileStore()
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { damageType, baseType } = useDetailDataStore()
|
||||
const { setResetData } = useCopyProfileStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
@@ -43,21 +44,20 @@ export default function ActionBar() {
|
||||
|
||||
const profileCurrent = useMemo(() => {
|
||||
if (!avatarSelected) return null;
|
||||
const avatar = avatars[avatarSelected.id];
|
||||
const avatar = avatars[avatarSelected.ID];
|
||||
return avatar?.profileList[avatar.profileSelect] || null;
|
||||
}, [avatarSelected, avatars]);
|
||||
|
||||
const listProfile = useMemo(() => {
|
||||
if (!avatarSelected) return [];
|
||||
const avatar = avatars[avatarSelected.id];
|
||||
const avatar = avatars[avatarSelected.ID];
|
||||
return avatar?.profileList || [];
|
||||
}, [avatarSelected, avatars]);
|
||||
|
||||
|
||||
|
||||
const handleUpdateProfile = () => {
|
||||
if (!profileName.trim()) return;
|
||||
if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.id]) {
|
||||
if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.ID]) {
|
||||
const newListProfile = [...listProfile]
|
||||
const newProfile = {
|
||||
profile_name: profileName,
|
||||
@@ -65,12 +65,12 @@ export default function ActionBar() {
|
||||
relics: {} as Record<string, RelicStore>
|
||||
}
|
||||
newListProfile.push(newProfile)
|
||||
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: newListProfile.length - 1 })
|
||||
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: newListProfile.length - 1 })
|
||||
toast.success("Profile created successfully")
|
||||
} else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) {
|
||||
const newListProfile = [...listProfile]
|
||||
newListProfile[profileEdit].profile_name = profileName;
|
||||
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile })
|
||||
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile })
|
||||
toast.success("Profile updated successfully")
|
||||
}
|
||||
handleCloseModal("update_profile_modal");
|
||||
@@ -99,20 +99,19 @@ export default function ActionBar() {
|
||||
|
||||
const handleProfileSelect = (profileId: number) => {
|
||||
if (!avatarSelected) return;
|
||||
if (avatars[avatarSelected.id].profileSelect === profileId) return;
|
||||
setAvatar({ ...avatars[avatarSelected.id], profileSelect: profileId })
|
||||
toast.success(`Profile changed to Profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}`)
|
||||
if (avatars[avatarSelected.ID].profileSelect === profileId) return;
|
||||
setAvatar({ ...avatars[avatarSelected.ID], profileSelect: profileId })
|
||||
toast.success(`Profile changed to Profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}`)
|
||||
}
|
||||
|
||||
|
||||
const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!avatarSelected || profileId == 0) return;
|
||||
if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}?`)) {
|
||||
if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}?`)) {
|
||||
const newListProfile = [...listProfile]
|
||||
newListProfile.splice(profileId, 1)
|
||||
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: profileId - 1 })
|
||||
toast.success(`Profile ${avatars[avatarSelected.id].profileList[profileId].profile_name} deleted successfully`)
|
||||
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: profileId - 1 })
|
||||
toast.success(`Profile ${avatars[avatarSelected.ID].profileList[profileId].profile_name} deleted successfully`)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -219,28 +218,26 @@ export default function ActionBar() {
|
||||
<div className="flex flex-wrap items-center gap-2 ">
|
||||
<div className="flex flex-wrap items-center h-full opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl">
|
||||
{avatarSelected && (
|
||||
<div className="flex items-center justify-start h-full w-full">
|
||||
<div className="flex flex-wrap items-center justify-start h-full w-full">
|
||||
<Image
|
||||
src={`/icon/${avatarSelected.damageType.toLowerCase()}.webp`}
|
||||
alt={'fire'}
|
||||
src={`${process.env.CDN_URL}/${damageType?.[avatarSelected?.DamageType]?.Icon}`}
|
||||
alt={'damage type'}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
className="h-10 w-10 object-contain"
|
||||
width={100}
|
||||
height={100}
|
||||
/>
|
||||
<p className="text-center font-bold text-xl">
|
||||
{transI18n(avatarSelected.baseType.toLowerCase())}
|
||||
<p className="text-center font-bold text-lg">
|
||||
{transI18n(avatarSelected.BaseType.toLowerCase())}
|
||||
</p>
|
||||
<div className="text-center font-bold text-xl">{" / "}</div>
|
||||
<div className="text-center font-bold text-lg">{" / "}</div>
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={getNameChar(locale, transI18n, avatarSelected).toWellFormed()}
|
||||
className={"font-bold text-xl"}
|
||||
className={"font-bold text-lg"}
|
||||
/>
|
||||
{avatarSelected?.id && (
|
||||
<div className="text-center italic text-sm ml-2"> {`(${avatarSelected.id})`}</div>
|
||||
)}
|
||||
<div className="text-center italic text-sm ml-2"> {`(${avatarSelected.ID})`}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -315,7 +312,7 @@ export default function ActionBar() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpenCopy(true)
|
||||
setListCopyAvatar(listRawAvatar)
|
||||
setResetData(baseType, damageType)
|
||||
handleShow("copy_profile_modal")
|
||||
}}
|
||||
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
"use client"
|
||||
import Image from "next/image"
|
||||
import { useEffect } from "react"
|
||||
import { useMemo } from "react"
|
||||
import CharacterCard from "../card/characterCard"
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
import { useTranslations } from "next-intl"
|
||||
|
||||
import useDetailDataStore from "@/stores/detailDataStore"
|
||||
import useCurrentDataStore from '@/stores/currentDataStore';
|
||||
import { calcRarity, getNameChar } from "@/helper"
|
||||
|
||||
export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
const {
|
||||
listAvatar,
|
||||
const {
|
||||
avatarSearch,
|
||||
mapAvatarElementActive,
|
||||
mapAvatarPathActive,
|
||||
setAvatarSearch,
|
||||
setAvatarSelected,
|
||||
setSkillSelected,
|
||||
setFilter,
|
||||
filter,
|
||||
listElement,
|
||||
listPath,
|
||||
setListElement,
|
||||
setListPath
|
||||
} = useAvatarStore()
|
||||
setMapAvatarElementActive,
|
||||
setMapAvatarPathActive,
|
||||
setSkillIDSelected,
|
||||
} = useCurrentDataStore()
|
||||
const { mapAvatar, baseType, damageType } = useDetailDataStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore()
|
||||
const {locale} = useLocaleStore()
|
||||
|
||||
const listAvatar = useMemo(() => {
|
||||
if (!mapAvatar || !locale || !transI18n) return []
|
||||
|
||||
useEffect(() => {
|
||||
setFilter({ ...filter, locale: locale, element: Object.keys(listElement).filter((key) => listElement[key]), path: Object.keys(listPath).filter((key) => listPath[key]) })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [locale, listElement, listPath])
|
||||
let list = Object.values(mapAvatar)
|
||||
|
||||
if (avatarSearch) {
|
||||
list = list.filter(item =>
|
||||
getNameChar(locale, transI18n, item)
|
||||
.toLowerCase()
|
||||
.includes(avatarSearch.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
const allElementFalse = !Object.values(mapAvatarElementActive).some(v => v)
|
||||
const allPathFalse = !Object.values(mapAvatarPathActive).some(v => v)
|
||||
|
||||
list = list.filter(item =>
|
||||
(allElementFalse || mapAvatarElementActive[item.DamageType]) &&
|
||||
(allPathFalse || mapAvatarPathActive[item.BaseType])
|
||||
)
|
||||
|
||||
list.sort((a, b) => {
|
||||
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
|
||||
if (r !== 0) return r
|
||||
return a.ID - b.ID
|
||||
})
|
||||
|
||||
return list
|
||||
}, [mapAvatar, mapAvatarElementActive, mapAvatarPathActive, avatarSearch, locale, transI18n])
|
||||
|
||||
|
||||
return (
|
||||
@@ -39,23 +64,23 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
<input type="text"
|
||||
placeholder={transI18n("placeholderCharacter")}
|
||||
className="input input-bordered input-primary w-full"
|
||||
value={filter.name}
|
||||
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })}
|
||||
value={avatarSearch}
|
||||
onChange={(e) => setAvatarSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 sm:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto">
|
||||
{Object.keys(listElement).map((key, index) => (
|
||||
{Object.entries(damageType).map(([key, value]) => (
|
||||
<div
|
||||
key={index}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setListElement({ ...listElement, [key]: !listElement[key] })
|
||||
setMapAvatarElementActive({ ...mapAvatarElementActive, [key]: !mapAvatarElementActive[key] })
|
||||
}}
|
||||
className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg"
|
||||
style={{
|
||||
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
|
||||
backgroundColor: mapAvatarElementActive[key] ? "#374151" : "#6B7280"
|
||||
}}>
|
||||
<Image
|
||||
src={`/icon/${key}.webp`}
|
||||
src={`${process.env.CDN_URL}/${value.Icon}`}
|
||||
alt={key}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
@@ -67,20 +92,20 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
|
||||
{Object.keys(listPath).map((key, index) => (
|
||||
{Object.entries(baseType).map(([key, value]) => (
|
||||
<div
|
||||
key={index}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
setMapAvatarPathActive({ ...mapAvatarPathActive, [key]: !mapAvatarPathActive[key] })
|
||||
}}
|
||||
className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
|
||||
backgroundColor: mapAvatarPathActive[key] ? "#374151" : "#6B7280"
|
||||
}}
|
||||
>
|
||||
|
||||
<Image
|
||||
src={`/icon/${key}.webp`}
|
||||
src={`${process.env.CDN_URL}/${value.Icon}`}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt={key}
|
||||
@@ -98,7 +123,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
{listAvatar.map((item, index) => (
|
||||
<div key={index} onClick={() => {
|
||||
setAvatarSelected(item);
|
||||
setSkillSelected(null)
|
||||
setSkillIDSelected(null)
|
||||
if (onClose) onClose()
|
||||
}}>
|
||||
<CharacterCard data={item} />
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client"
|
||||
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import LightconeBar from '../lightconeBar'
|
||||
import useLightconeStore from '@/stores/lightconeStore'
|
||||
import { calcPromotion, calcRarity, replaceByParam } from '@/helper';
|
||||
import { getSkillTree } from '@/helper/getSkillTree';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import ParseText from '../parseText';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import useModelStore from '@/stores/modelStore';
|
||||
import useMazeStore from '@/stores/mazeStore';
|
||||
import Image from 'next/image';
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { getLocaleName } from "@/helper/getName";
|
||||
export default function AvatarInfo() {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
|
||||
const { Technique } = useMazeStore()
|
||||
const { avatarSelected, setResetDataLightcone } = useCurrentDataStore()
|
||||
const { avatars, setAvatars, setAvatar } = useUserDataStore()
|
||||
const { isOpenLightcone, setIsOpenLightcone } = useModelStore()
|
||||
const { listLightcone, mapLightconeInfo, setDefaultFilter } = useLightconeStore()
|
||||
const { mapLightCone, baseType } = useDetailDataStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
const lightcone = useMemo(() => {
|
||||
if (!avatarSelected) return null;
|
||||
const avatar = avatars[avatarSelected.id];
|
||||
const avatar = avatars[avatarSelected.ID];
|
||||
return avatar?.profileList[avatar.profileSelect]?.lightcone || null;
|
||||
}, [avatarSelected, avatars]);
|
||||
|
||||
const lightconeDetail = useMemo(() => {
|
||||
if (!lightcone) return null;
|
||||
return listLightcone.find((item) => Number(item.id) === Number(lightcone.item_id)) || null;
|
||||
}, [lightcone, listLightcone]);
|
||||
|
||||
|
||||
if (!mapLightCone || !lightcone?.item_id) return null;
|
||||
return mapLightCone?.[lightcone.item_id.toString()] || null;
|
||||
}, [lightcone, mapLightCone]);
|
||||
|
||||
const handleShow = (modalId: string) => {
|
||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||
@@ -74,7 +71,7 @@ export default function AvatarInfo() {
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
|
||||
{avatarSelected && avatars[avatarSelected?.id || ""] && (
|
||||
{avatarSelected && avatars[avatarSelected?.ID.toString() || ""] && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
|
||||
<div className="m-2 min-h-96">
|
||||
<div className="container">
|
||||
@@ -99,25 +96,25 @@ export default function AvatarInfo() {
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-medium">{transI18n("characterLevel")}</span>
|
||||
<span className="label-text-alt text-info font-mono">{avatars[avatarSelected?.id || ""]?.level}/80</span>
|
||||
<span className="label-text-alt text-info font-mono">{avatars[avatarSelected?.ID.toString() || ""]?.level}/80</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="80"
|
||||
value={avatars[avatarSelected?.id || ""]?.level}
|
||||
value={avatars[avatarSelected?.ID.toString() || ""]?.level}
|
||||
onChange={(e) => {
|
||||
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
|
||||
|
||||
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
|
||||
setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
|
||||
}}
|
||||
className="input input-bordered w-full pr-16 font-mono"
|
||||
placeholder={transI18n("placeholderLevel")}
|
||||
/>
|
||||
<div
|
||||
onClick={() => {
|
||||
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: 80, promotion: calcPromotion(80) } });
|
||||
setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: 80, promotion: calcPromotion(80) } });
|
||||
}}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/60 cursor-pointer">
|
||||
<span className="text-sm">{transI18n("max")}</span>
|
||||
@@ -128,10 +125,10 @@ export default function AvatarInfo() {
|
||||
type="range"
|
||||
min="1"
|
||||
max="80"
|
||||
value={avatars[avatarSelected?.id || ""]?.level}
|
||||
value={avatars[avatarSelected?.ID.toString() || ""]?.level}
|
||||
onChange={(e) => {
|
||||
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
|
||||
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
|
||||
setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
|
||||
}}
|
||||
className="range range-info range-sm w-full"
|
||||
/>
|
||||
@@ -150,33 +147,33 @@ export default function AvatarInfo() {
|
||||
<label className="label">
|
||||
<span className="label-text font-medium">{transI18n("currentEnergy")}</span>
|
||||
<span className="label-text text-warning font-mono">
|
||||
{Math.round(avatars[avatarSelected?.id || ""]?.sp_value)}/{avatars[avatarSelected?.id || ""]?.sp_max}
|
||||
{Math.round(avatars[avatarSelected?.ID.toString() || ""]?.sp_value)}/{avatars[avatarSelected?.ID.toString() || ""]?.sp_max}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={avatars[avatarSelected?.id || ""]?.sp_max}
|
||||
value={avatars[avatarSelected?.id || ""]?.sp_value}
|
||||
max={avatars[avatarSelected?.ID.toString() || ""]?.sp_max}
|
||||
value={avatars[avatarSelected?.ID.toString() || ""]?.sp_value}
|
||||
onChange={(e) => {
|
||||
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return
|
||||
const newSpValue = Math.min(avatars[avatarSelected?.id || ""]?.sp_max, Math.max(0, parseInt(e.target.value) || 0));
|
||||
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue } });
|
||||
if (!avatars[avatarSelected?.ID.toString() || ""]?.can_change_sp) return
|
||||
const newSpValue = Math.min(avatars[avatarSelected?.ID.toString() || ""]?.sp_max, Math.max(0, parseInt(e.target.value) || 0));
|
||||
setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], sp_value: newSpValue } });
|
||||
}}
|
||||
className="range range-warning range-sm w-full"
|
||||
/>
|
||||
<div className="flex justify-between text-sm text-base-content/60 mt-1">
|
||||
<span>0%</span>
|
||||
<span className="font-mono text-warning">{((avatars[avatarSelected?.id || ""]?.sp_value / avatars[avatarSelected?.id || ""]?.sp_max) * 100).toFixed(1)}%</span>
|
||||
<span className="font-mono text-warning">{((avatars[avatarSelected?.ID.toString() || ""]?.sp_value / avatars[avatarSelected?.ID.toString() || ""]?.sp_max) * 100).toFixed(1)}%</span>
|
||||
<span>100%</span>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<button
|
||||
className="btn btn-sm btn-outline btn-warning"
|
||||
onClick={() => {
|
||||
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return
|
||||
const newSpValue = Math.ceil(avatars[avatarSelected?.id || ""]?.sp_max / 2);
|
||||
const newAvatar = { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue }
|
||||
if (!avatars[avatarSelected?.ID.toString() || ""]?.can_change_sp) return
|
||||
const newSpValue = Math.ceil(avatars[avatarSelected?.ID.toString() || ""]?.sp_max / 2);
|
||||
const newAvatar = { ...avatars[avatarSelected?.ID.toString() || ""], sp_value: newSpValue }
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
>
|
||||
@@ -203,11 +200,11 @@ export default function AvatarInfo() {
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={avatars[avatarSelected?.id || ""]?.techniques.length > 0}
|
||||
checked={(!avatarSelected || avatarSelected?.MazeBuff?.length > 0)}
|
||||
onChange={(e) => {
|
||||
if (!Technique[avatarSelected?.id || ""] || Technique[avatarSelected?.id || ""]?.maze_buff.length === 0) return
|
||||
const techniques = e.target.checked ? Technique[avatarSelected?.id || ""]?.maze_buff : [];
|
||||
const newAvatar = { ...avatars[avatarSelected?.id || ""], techniques };
|
||||
if (!avatarSelected || avatarSelected?.MazeBuff?.length > 0) return
|
||||
const techniques = e.target.checked ? avatarSelected?.MazeBuff : [];
|
||||
const newAvatar = { ...avatars[avatarSelected?.ID.toString() || ""], techniques: techniques };
|
||||
setAvatar(newAvatar);
|
||||
}}
|
||||
className="toggle toggle-accent"
|
||||
@@ -220,7 +217,7 @@ export default function AvatarInfo() {
|
||||
</div>
|
||||
|
||||
{/* Enhancement Selection */}
|
||||
{Object.entries(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).length > 0 && (
|
||||
{avatarSelected?.Enhanced && (
|
||||
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
|
||||
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<svg className="w-5 h-5 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -233,16 +230,16 @@ export default function AvatarInfo() {
|
||||
<label className="label">
|
||||
<span className="label-text font-medium">{transI18n("enhancementLevel")}</span>
|
||||
<span className="label-text-alt text-secondary font-mono">
|
||||
{avatars[avatarSelected?.id || ""]?.enhanced || transI18n("origin")}
|
||||
{avatars[avatarSelected?.ID.toString() || ""]?.enhanced || transI18n("origin")}
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
value={avatars[avatarSelected?.id || ""]?.enhanced || ""}
|
||||
value={avatars[avatarSelected?.ID.toString() || ""]?.enhanced || ""}
|
||||
onChange={(e) => {
|
||||
const newAvatar = avatars[avatarSelected?.id || ""]
|
||||
const newAvatar = avatars[avatarSelected?.ID.toString() || ""]
|
||||
if (newAvatar) {
|
||||
newAvatar.enhanced = e.target.value
|
||||
const skillTree = getSkillTree(e.target.value)
|
||||
const skillTree = getSkillTree(avatarSelected, e.target.value)
|
||||
if (skillTree) {
|
||||
newAvatar.data.skills = skillTree
|
||||
}
|
||||
@@ -252,7 +249,7 @@ export default function AvatarInfo() {
|
||||
className="select select-bordered select-secondary"
|
||||
>
|
||||
<option value="">{transI18n("origin")}</option>
|
||||
{Object.keys(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).map((key) => (
|
||||
{Object.keys(avatarSelected?.Enhanced || {}).map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
@@ -308,7 +305,7 @@ export default function AvatarInfo() {
|
||||
onChange={(e) => {
|
||||
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
|
||||
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
|
||||
const newAvatar = { ...avatars[avatarSelected.id] }
|
||||
const newAvatar = { ...avatars[avatarSelected.ID] }
|
||||
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
@@ -318,7 +315,7 @@ export default function AvatarInfo() {
|
||||
<div
|
||||
onClick={() => {
|
||||
const newLightcone = { ...lightcone, level: 80, promotion: calcPromotion(80) }
|
||||
const newAvatar = { ...avatars[avatarSelected.id] }
|
||||
const newAvatar = { ...avatars[avatarSelected.ID] }
|
||||
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
@@ -335,7 +332,7 @@ export default function AvatarInfo() {
|
||||
onChange={(e) => {
|
||||
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
|
||||
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
|
||||
const newAvatar = { ...avatars[avatarSelected.id] }
|
||||
const newAvatar = { ...avatars[avatarSelected.ID] }
|
||||
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
@@ -360,7 +357,7 @@ export default function AvatarInfo() {
|
||||
key={r}
|
||||
onClick={() => {
|
||||
const newLightcone = { ...lightcone, rank: r }
|
||||
const newAvatar = { ...avatars[avatarSelected.id] }
|
||||
const newAvatar = { ...avatars[avatarSelected.ID] }
|
||||
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
@@ -391,8 +388,7 @@ export default function AvatarInfo() {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (avatarSelected) {
|
||||
const newDefaultFilter = { path: [avatarSelected.baseType.toLowerCase()], rarity: [] }
|
||||
setDefaultFilter(newDefaultFilter)
|
||||
setResetDataLightcone(avatarSelected, baseType)
|
||||
handleShow("action_detail_modal")
|
||||
}
|
||||
}}
|
||||
@@ -405,7 +401,7 @@ export default function AvatarInfo() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newAvatar = { ...avatars[avatarSelected.id] }
|
||||
const newAvatar = { ...avatars[avatarSelected.ID] }
|
||||
newAvatar.profileList[newAvatar.profileSelect].lightcone = null
|
||||
setAvatar(newAvatar)
|
||||
}}
|
||||
@@ -428,7 +424,7 @@ export default function AvatarInfo() {
|
||||
width={904}
|
||||
height={1260}
|
||||
priority
|
||||
src={`${process.env.CDN_URL}/${lightconeDetail.image}`}
|
||||
src={`${process.env.CDN_URL}/${lightconeDetail?.Image?.ImagePath}`}
|
||||
className="w-full h-full rounded-lg object-cover shadow-lg"
|
||||
alt="Lightcone"
|
||||
/>
|
||||
@@ -438,20 +434,20 @@ export default function AvatarInfo() {
|
||||
{/* Lightcone Info & Controls */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Basic Info */}
|
||||
{mapLightconeInfo[lightcone.item_id] && (
|
||||
{lightconeDetail && (
|
||||
<div className="bg-base-300 rounded-xl p-6 border border-base-content/10">
|
||||
<div className="flex flex-wrap items-center gap-3 mb-4">
|
||||
<h3 className="text-2xl font-bold text-base-content">
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={mapLightconeInfo[lightcone.item_id].Name}
|
||||
text={getLocaleName(locale, lightconeDetail.Name)}
|
||||
/>
|
||||
</h3>
|
||||
<div className="badge badge-outline badge-lg">
|
||||
{transI18n(mapLightconeInfo[lightcone.item_id].BaseType.toLowerCase())}
|
||||
{transI18n(lightconeDetail.BaseType.toLowerCase())}
|
||||
</div>
|
||||
<div className="badge badge-outline badge-lg">
|
||||
{calcRarity(mapLightconeInfo[lightcone.item_id].Rarity) + "⭐"}
|
||||
{calcRarity(lightconeDetail.Rarity) + "⭐"}
|
||||
</div>
|
||||
<div className="badge badge-outline badge-lg">
|
||||
{"id: " + lightcone.item_id}
|
||||
@@ -463,8 +459,8 @@ export default function AvatarInfo() {
|
||||
className="text-base-content/80 leading-relaxed"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
mapLightconeInfo[lightcone.item_id].Refinements.Desc,
|
||||
mapLightconeInfo[lightcone.item_id].Refinements.Level[lightcone.rank.toString()]?.ParamList || []
|
||||
getLocaleName(locale, lightconeDetail.Skills.Desc),
|
||||
lightconeDetail.Skills.Level[lightcone.rank.toString()]?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
@@ -491,8 +487,7 @@ export default function AvatarInfo() {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (avatarSelected) {
|
||||
const newDefaultFilter = { path: [avatarSelected.baseType.toLowerCase()], rarity: [] }
|
||||
setDefaultFilter(newDefaultFilter)
|
||||
setResetDataLightcone(avatarSelected, baseType)
|
||||
handleShow("action_detail_modal")
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
import { getNameChar } from '@/helper';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { CharacterBasic } from '@/types';
|
||||
import { AvatarDetail } from '@/types';
|
||||
import ParseText from '../parseText';
|
||||
import Image from 'next/image';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
|
||||
interface CharacterCardProps {
|
||||
data: CharacterBasic
|
||||
data: AvatarDetail
|
||||
}
|
||||
|
||||
export default function CharacterCard({ data }: CharacterCardProps) {
|
||||
const { locale } = useLocaleStore();
|
||||
const transI18n = useTranslations("DataPage");
|
||||
const { baseType, damageType } = useDetailDataStore()
|
||||
|
||||
return (
|
||||
<li
|
||||
@@ -23,7 +25,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
|
||||
hover:scale-105 cursor-pointer min-h-45 sm:min-h-45 md:min-h-52.5 lg:min-h-55 xl:min-h-60 2xl:min-h-85"
|
||||
>
|
||||
<div
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerAvatarRarityType5"
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5"
|
||||
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
|
||||
: "from-purple-400 via-purple-600/70 to-purple-800/50"
|
||||
}`}
|
||||
@@ -35,7 +37,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
|
||||
height={512}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${data.icon}`}
|
||||
src={`${process.env.CDN_URL}/${data.Image.AvatarIconPath}`}
|
||||
priority={true}
|
||||
className="rounded-md w-full h-full object-contain"
|
||||
alt="ALT"
|
||||
@@ -45,18 +47,18 @@ export default function CharacterCard({ data }: CharacterCardProps) {
|
||||
height={32}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${data.damageType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${damageType?.[data.DamageType].Icon}`}
|
||||
className="absolute top-0 left-0 w-6 h-6 rounded-full"
|
||||
alt={data.damageType.toLowerCase()}
|
||||
alt={data.DamageType.toLowerCase()}
|
||||
/>
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${data.baseType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${baseType?.[data.BaseType].Icon}`}
|
||||
className="absolute top-0 right-0 w-6 h-6 rounded-full"
|
||||
alt={data.baseType.toLowerCase()}
|
||||
alt={data.BaseType.toLowerCase()}
|
||||
style={{
|
||||
boxShadow: "inset 0 0 8px 4px #9CA3AF"
|
||||
}}
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
import React from 'react';
|
||||
import { CharacterInfoCardType } from '@/types';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import useAvatarStore from '@/stores/avatarStore';
|
||||
import useLightconeStore from '@/stores/lightconeStore';
|
||||
import Image from 'next/image';
|
||||
import ParseText from '../parseText';
|
||||
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
import { getLocaleName } from '@/helper/getName';
|
||||
|
||||
export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) {
|
||||
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
|
||||
const { mapAvatarInfo } = useAvatarStore();
|
||||
const { mapLightconeInfo } = useLightconeStore();
|
||||
const { mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
return (
|
||||
@@ -29,8 +27,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
|
||||
<div className="relative mb-4">
|
||||
<div className="w-full h-48 rounded-lg overflow-hidden relative">
|
||||
<Image
|
||||
src={`${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${character.avatar_id}.png`}
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""}
|
||||
src={`${process.env.CDN_URL}/${mapAvatar?.[character.avatar_id.toString()]?.Image?.AvatarIconPath}`}
|
||||
alt={getLocaleName(locale, mapAvatar?.[character.avatar_id.toString()]?.Name)}
|
||||
width={376}
|
||||
height={512}
|
||||
unoptimized
|
||||
@@ -42,18 +40,18 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
|
||||
height={48}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${damageType?.[mapAvatar?.[character.avatar_id.toString()]?.DamageType || ""].Icon}`}
|
||||
className="absolute top-0 left-0 w-10 h-10 rounded-full"
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}
|
||||
alt={mapAvatar[character.avatar_id.toString()]?.DamageType.toLowerCase()}
|
||||
/>
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${baseType?.[mapAvatar?.[character.avatar_id.toString()]?.BaseType || ""].Icon}`}
|
||||
className="absolute top-0 right-0 w-10 h-10 rounded-full"
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}
|
||||
alt={mapAvatar[character.avatar_id.toString()]?.BaseType.toLowerCase()}
|
||||
style={{
|
||||
boxShadow: "inset 0 0 8px 4px #9CA3AF"
|
||||
}}
|
||||
@@ -65,7 +63,7 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
|
||||
<div className="w-full rounded-lg flex items-center justify-center mb-2">
|
||||
<div className="text-center">
|
||||
<ParseText className="text-lg font-bold"
|
||||
text={mapAvatarInfo[character.avatar_id.toString()]?.Name}
|
||||
text={getLocaleName(locale, mapAvatar[character.avatar_id.toString()]?.Name)}
|
||||
locale={locale}
|
||||
/>
|
||||
<div className="text-base mb-1">Lv.{character.level} E{character.rank}</div>
|
||||
@@ -101,8 +99,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${character.lightcone.item_id}.png`}
|
||||
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
|
||||
src={`${process.env.CDN_URL}/${mapLightCone?.[character.lightcone.item_id.toString()]?.Image?.ImagePath}`}
|
||||
alt={getLocaleName(locale, mapLightCone?.[character.lightcone.item_id.toString()]?.Name)}
|
||||
width={348}
|
||||
height={408}
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
@@ -113,7 +111,7 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold">
|
||||
<ParseText
|
||||
text={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
|
||||
text={getLocaleName(locale, mapLightCone[character.lightcone.item_id.toString()]?.Name)}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
|
||||
import { getLocaleName } from '@/helper';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { LightConeBasic } from '@/types';
|
||||
import ParseText from '../parseText';
|
||||
import Image from 'next/image';
|
||||
import { LightConeDetail } from '@/types';
|
||||
|
||||
interface LightconeCardProps {
|
||||
data: LightConeBasic
|
||||
data: LightConeDetail
|
||||
}
|
||||
|
||||
export default function LightconeCard({ data }: LightconeCardProps) {
|
||||
|
||||
const { locale } = useLocaleStore();
|
||||
const text = getLocaleName(locale, data)
|
||||
const text = getLocaleName(locale, data.Name)
|
||||
return (
|
||||
<li className="z-10 flex flex-col items-center rounded-md shadow-lg
|
||||
bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300
|
||||
hover:scale-105 cursor-pointer min-h-55"
|
||||
>
|
||||
<div
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerLightconeRarity5"
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerLightconeRarity5"
|
||||
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
|
||||
: data.rank === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" :
|
||||
: data.Rarity === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" :
|
||||
"from-blue-400 via-blue-600/70 to-blue-800/50"
|
||||
}`}
|
||||
>
|
||||
@@ -30,7 +30,7 @@ export default function LightconeCard({ data }: LightconeCardProps) {
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
loading="lazy"
|
||||
src={`${process.env.CDN_URL}/${data.thumbnail}`}
|
||||
src={`${process.env.CDN_URL}/${data?.Image?.ThumbnailPath}`}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
width={348}
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
import React from 'react';
|
||||
import { AvatarProfileCardType } from '@/types';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import useLightconeStore from '@/stores/lightconeStore';
|
||||
import Image from 'next/image';
|
||||
import ParseText from '../parseText';
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
import { getLocaleName } from '@/helper/getName';
|
||||
|
||||
|
||||
export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) {
|
||||
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
|
||||
const { mapLightconeInfo } = useLightconeStore();
|
||||
const { mapLightCone } = useDetailDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
return (
|
||||
@@ -30,7 +31,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`}
|
||||
alt={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
|
||||
alt={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
|
||||
width={348}
|
||||
height={408}
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
@@ -41,7 +42,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold">
|
||||
<ParseText
|
||||
text={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
|
||||
text={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
@@ -59,7 +60,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
|
||||
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`}
|
||||
alt="Relic"
|
||||
width={124}
|
||||
height={124}
|
||||
|
||||
@@ -1,29 +1,56 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import {
|
||||
useFetchASData,
|
||||
useFetchASGroupData,
|
||||
useFetchAvatarData,
|
||||
useFetchChangelog,
|
||||
useFetchConfigData,
|
||||
useFetchLightconeData,
|
||||
useFetchMOCData,
|
||||
useFetchMOCGroupData,
|
||||
useFetchMonsterData,
|
||||
useFetchPEAKData,
|
||||
useFetchPFData,
|
||||
useFetchRelicData
|
||||
} from "@/lib/hooks";
|
||||
useFetchPeakGroupData,
|
||||
useFetchPFGroupData,
|
||||
useFetchRelicSetData
|
||||
} from "@/lib/hooks"
|
||||
|
||||
export default function ClientDataFetcher() {
|
||||
useFetchConfigData();
|
||||
useFetchAvatarData();
|
||||
useFetchLightconeData();
|
||||
useFetchRelicData();
|
||||
useFetchMonsterData();
|
||||
useFetchPFData();
|
||||
useFetchMOCData();
|
||||
useFetchASData();
|
||||
useFetchPEAKData();
|
||||
useFetchChangelog();
|
||||
export default function ClientDataFetcher({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const q1 = useFetchConfigData()
|
||||
const q2 = useFetchAvatarData()
|
||||
const q3 = useFetchLightconeData()
|
||||
const q4 = useFetchRelicSetData()
|
||||
const q5 = useFetchMonsterData()
|
||||
const q6 = useFetchPFGroupData()
|
||||
const q7 = useFetchMOCGroupData()
|
||||
const q8 = useFetchASGroupData()
|
||||
const q9 = useFetchPeakGroupData()
|
||||
const q10 = useFetchChangelog()
|
||||
|
||||
return null;
|
||||
}
|
||||
const queries = [q1,q2,q3,q4,q5,q6,q7,q8,q9,q10]
|
||||
|
||||
const loading = queries.some(q => q.isLoading)
|
||||
|
||||
const progress =
|
||||
(queries.filter(q => q.isSuccess).length / queries.length) * 100
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-screen flex-col items-center justify-center gap-4">
|
||||
<div className="text-lg font-semibold">Loading data...</div>
|
||||
|
||||
<progress
|
||||
className="progress progress-primary w-56"
|
||||
value={progress}
|
||||
max="100"
|
||||
/>
|
||||
|
||||
<div>{Math.floor(progress)}%</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client"
|
||||
import { replaceByParam } from "@/helper";
|
||||
import useListAvatarStore from "@/stores/avatarStore";
|
||||
import { replaceByParam, getLocaleName } from '@/helper';
|
||||
import Image from "next/image";
|
||||
import ParseText from "../parseText";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
|
||||
|
||||
export default function EidolonsInfo() {
|
||||
const { avatarSelected, mapAvatarInfo } = useListAvatarStore()
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { setAvatars, avatars } = useUserDataStore()
|
||||
|
||||
const charRank = useMemo(() => {
|
||||
if (!avatarSelected) return null;
|
||||
const avatar = avatars[avatarSelected.id];
|
||||
const avatar = avatars[avatarSelected.ID];
|
||||
if (avatar?.enhanced != "") {
|
||||
return mapAvatarInfo[avatarSelected.id]?.Enhanced[avatar?.enhanced].Ranks
|
||||
return avatarSelected?.Enhanced?.[avatar?.enhanced]?.Ranks
|
||||
}
|
||||
return mapAvatarInfo[avatarSelected.id]?.Ranks
|
||||
}, [avatarSelected, avatars, locale, mapAvatarInfo]);
|
||||
return avatarSelected?.Ranks
|
||||
}, [avatarSelected, avatars]);
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
|
||||
@@ -32,22 +31,22 @@ export default function EidolonsInfo() {
|
||||
{transI18n("eidolons")}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
|
||||
{charRank && avatars[avatarSelected?.id || ""] && (
|
||||
{charRank && avatars[avatarSelected?.ID || ""] && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(charRank || {}).map(([key, rank]) => (
|
||||
<div key={key}
|
||||
className="flex flex-col items-center cursor-pointer hover:scale-105"
|
||||
onClick={() => {
|
||||
let newRank = Number(key)
|
||||
if (avatars[avatarSelected?.id || ""]?.data?.rank == Number(key)) {
|
||||
if (avatars[avatarSelected?.ID || ""]?.data?.rank == Number(key)) {
|
||||
newRank = Number(key) - 1
|
||||
}
|
||||
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], data: { ...avatars[avatarSelected?.id || ""].data, rank: newRank } } })
|
||||
setAvatars({ ...avatars, [avatarSelected?.ID || ""]: { ...avatars[avatarSelected?.ID || ""], data: { ...avatars[avatarSelected?.ID || ""].data, rank: newRank } } })
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.id || ""]?.data?.rank ? "" : "grayscale"}`}
|
||||
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.png`}
|
||||
className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.ID.toString() || ""]?.data?.rank ? "" : "grayscale"}`}
|
||||
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.ID}/${avatarSelected?.ID}_Rank_${key}.png`}
|
||||
alt={`Rank ${key}`}
|
||||
priority
|
||||
unoptimized
|
||||
@@ -60,12 +59,12 @@ export default function EidolonsInfo() {
|
||||
<span className="inline-block text-indigo-500">{key}.</span>
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={rank.Name}
|
||||
text={getLocaleName(locale, rank.Name)}
|
||||
className="text-center text-base font-normal leading-tight"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm font-normal">
|
||||
<div dangerouslySetInnerHTML={{ __html: replaceByParam(rank.Desc, rank.ParamList) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, rank.Desc), rank.Param) }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -3,34 +3,28 @@ import { motion } from "framer-motion"
|
||||
import { EyeOff, Eye, Hammer, RefreshCw, ShieldBan, User, Swords, SkipForward, BowArrow, Info, RouteIcon, Search } from "lucide-react"
|
||||
import useGlobalStore from '@/stores/globalStore'
|
||||
import { useTranslations } from "next-intl"
|
||||
import useEventStore from "@/stores/eventStore"
|
||||
import { getLocaleName, getNameChar } from "@/helper"
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
import SelectCustomImage from "../select/customSelectImage"
|
||||
import { useMemo, useState } from "react"
|
||||
import useMazeStore from "@/stores/mazeStore"
|
||||
import useDetailDataStore from "@/stores/detailDataStore"
|
||||
|
||||
export default function ExtraSettingBar() {
|
||||
const { extraData, setExtraData } = useGlobalStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { PEAKEvent } = useEventStore()
|
||||
const { listAvatar } = useAvatarStore()
|
||||
const { mapAvatar, mapPeak, stage, baseType } = useDetailDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const [showSearchStage, setShowSearchStage] = useState(false)
|
||||
const [isChildClick, setIsChildClick] = useState(false)
|
||||
const [stageSearchTerm, setStageSearchTerm] = useState("")
|
||||
const [stagePage, setStagePage] = useState(1)
|
||||
const { Stage } = useMazeStore()
|
||||
|
||||
const pageSize = 30
|
||||
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
|
||||
id: stage.stage_id.toString(),
|
||||
name: `${stage.stage_type} (${stage.stage_id})`,
|
||||
})), [Stage])
|
||||
const stageList = useMemo(() => Object.values(stage), [stage])
|
||||
|
||||
const filteredStages = useMemo(() => stageList.filter((s) =>
|
||||
s.name.toLowerCase().includes(stageSearchTerm.toLowerCase())
|
||||
), [stageList, stageSearchTerm])
|
||||
getLocaleName(locale, s.Name).toLowerCase().includes(stageSearchTerm.toLowerCase())
|
||||
), [stageList, stageSearchTerm, locale])
|
||||
|
||||
const paginatedStages = useMemo(() => filteredStages.slice(
|
||||
(stagePage - 1) * pageSize,
|
||||
@@ -122,7 +116,7 @@ export default function ExtraSettingBar() {
|
||||
}}
|
||||
>
|
||||
<Search className="w-6 h-6" />
|
||||
<span className="text-left"> {transI18n("stage")}: {stageList.find((s) => s.id === extraData?.theory_craft?.stage_id?.toString())?.name || transI18n("selectStage")}</span>
|
||||
<span className="text-left"> {transI18n("stage")}: {getLocaleName(locale, stageList.find((s) => s.ID.toString() === extraData?.theory_craft?.stage_id?.toString())?.Name) || transI18n("selectStage")}</span>
|
||||
</button>
|
||||
</div>
|
||||
{showSearchStage && (
|
||||
@@ -158,17 +152,17 @@ export default function ExtraSettingBar() {
|
||||
<>
|
||||
{paginatedStages.map((stage) => (
|
||||
<div
|
||||
key={stage.id}
|
||||
key={stage.ID}
|
||||
className="p-2 hover:bg-primary/20 rounded cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsChildClick(true)
|
||||
|
||||
if (extraData?.theory_craft?.stage_id !== Number(stage.id)) {
|
||||
if (extraData?.theory_craft?.stage_id !== Number(stage.ID)) {
|
||||
setExtraData({
|
||||
...extraData,
|
||||
theory_craft: {
|
||||
stage_id: Number(stage.id),
|
||||
stage_id: Number(stage.ID),
|
||||
cycle_count: extraData?.theory_craft?.cycle_count || 1,
|
||||
mode: extraData?.theory_craft?.mode || false,
|
||||
hp: extraData?.theory_craft?.hp || {}
|
||||
@@ -179,7 +173,7 @@ export default function ExtraSettingBar() {
|
||||
onChangeSearch("")
|
||||
}}
|
||||
>
|
||||
{stage.name}
|
||||
{getLocaleName(locale, stage.Name)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -238,10 +232,10 @@ export default function ExtraSettingBar() {
|
||||
<User className="text-warning" size={20} />
|
||||
<span className="label-text font-semibold">{transI18n("mainPath")}</span>
|
||||
<SelectCustomImage
|
||||
customSet={listAvatar.filter((it) => extraData?.multi_path?.multi_path_main?.includes(Number(it.id))).map((it) => ({
|
||||
value: it.id,
|
||||
customSet={Object.values(mapAvatar).filter((it) => extraData?.multi_path?.multi_path_main?.includes(Number(it.ID))).map((it) => ({
|
||||
value: it.ID.toString(),
|
||||
label: getNameChar(locale, transI18n, it),
|
||||
imageUrl: `/icon/${it.baseType.toLowerCase()}.webp`
|
||||
imageUrl: `${process.env.CDN_URL}/${baseType?.[it.BaseType].Icon}`
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={extraData?.multi_path?.main?.toString() || ""}
|
||||
@@ -268,10 +262,10 @@ export default function ExtraSettingBar() {
|
||||
<BowArrow className="text-info" size={20} />
|
||||
<span className="label-text font-semibold">{transI18n("march7Path")}</span>
|
||||
<SelectCustomImage
|
||||
customSet={listAvatar.filter((it) => extraData?.multi_path?.multi_path_march_7?.includes(Number(it.id))).map((it) => ({
|
||||
value: it.id,
|
||||
customSet={Object.values(mapAvatar).filter((it) => extraData?.multi_path?.multi_path_march_7?.includes(Number(it.ID))).map((it) => ({
|
||||
value: it.ID.toString(),
|
||||
label: getNameChar(locale, transI18n, it),
|
||||
imageUrl: `/icon/${it.baseType.toLowerCase()}.webp`
|
||||
imageUrl: `${process.env.CDN_URL}/${baseType?.[it.BaseType].Icon}`
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""}
|
||||
@@ -318,9 +312,9 @@ export default function ExtraSettingBar() {
|
||||
})
|
||||
}
|
||||
>
|
||||
{PEAKEvent.filter(event => extraData?.challenge?.challenge_peak_group_id_list?.includes(Number(event.id))).map(event => (
|
||||
<option key={event.id} value={event.id}>
|
||||
{getLocaleName(locale, event)} ({event.id})
|
||||
{Object.values(mapPeak).filter(event => extraData?.challenge?.challenge_peak_group_id_list?.includes(Number(event.ID))).map(event => (
|
||||
<option key={event.ID} value={event.ID}>
|
||||
{getLocaleName(locale, event.Name)} ({event.ID})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -8,7 +8,6 @@ import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import EnkaImport from "../importBar/enka";
|
||||
@@ -564,19 +563,6 @@ export default function Header() {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* GitHub Link */}
|
||||
<Link
|
||||
className='flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center tooltip tooltip-bottom'
|
||||
href={"https://github.com/AzenKain/Firefly-Srtools"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-tip="Github"
|
||||
>
|
||||
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{modalConfigs?.map(({ id, title, onClose, content }) => (
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
"use client"
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import useCopyProfileStore from "@/stores/copyProfile";
|
||||
import ProfileCard from "../card/profileCard";
|
||||
import { AvatarProfileCardType, AvatarProfileStore } from "@/types";
|
||||
import Image from "next/image";
|
||||
import useListAvatarStore from "@/stores/avatarStore";
|
||||
import { getNameChar } from "@/helper";
|
||||
import { getNameChar, calcRarity } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import SelectCustomImage from "../select/customSelectImage";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function CopyImport() {
|
||||
const { avatars, setAvatar } = useUserDataStore();
|
||||
const { avatarSelected } = useListAvatarStore()
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { mapAvatar, baseType, damageType } = useDetailDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
selectedProfiles,
|
||||
listCopyAvatar,
|
||||
avatarCopySelected,
|
||||
setSelectedProfiles,
|
||||
filterCopy,
|
||||
setFilterCopy,
|
||||
setAvatarCopySelected,
|
||||
listElement,
|
||||
listPath,
|
||||
@@ -37,6 +36,22 @@ export default function CopyImport() {
|
||||
text: ""
|
||||
})
|
||||
|
||||
const listAvatar = useMemo(() => {
|
||||
if (!mapAvatar || !locale || !transI18n) return []
|
||||
let list = Object.values(mapAvatar);
|
||||
const allElementFalse = !Object.values(listElement).some(v => v)
|
||||
const allPathFalse = !Object.values(listPath).some(v => v)
|
||||
const allRarityFalse = !Object.values(listRank).some(v => v)
|
||||
list = list.filter(item => (allElementFalse || listElement[item.DamageType]) && (allPathFalse || listPath[item.BaseType]) && (allRarityFalse || listRank[calcRarity(item.Rarity)]))
|
||||
list.sort((a, b) => {
|
||||
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
|
||||
if (r !== 0) return r
|
||||
return a.ID - b.ID
|
||||
})
|
||||
return list
|
||||
}, [mapAvatar, listElement, listPath, listRank, locale, transI18n])
|
||||
|
||||
|
||||
const handleProfileToggle = (profile: AvatarProfileCardType) => {
|
||||
if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) {
|
||||
setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key));
|
||||
@@ -45,17 +60,6 @@ export default function CopyImport() {
|
||||
setSelectedProfiles([...selectedProfiles, profile]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFilterCopy({
|
||||
...filterCopy,
|
||||
locale: locale,
|
||||
path: Object.keys(listPath).filter((key) => listPath[key]),
|
||||
element: Object.keys(listElement).filter((key) => listElement[key]),
|
||||
rarity: Object.keys(listRank).filter((key) => listRank[key])
|
||||
})
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPath, listRank, listElement, locale, setFilterCopy])
|
||||
|
||||
const clearSelection = () => {
|
||||
setSelectedProfiles([]);
|
||||
@@ -67,7 +71,7 @@ export default function CopyImport() {
|
||||
|
||||
const selectAll = () => {
|
||||
if (avatarCopySelected) {
|
||||
setSelectedProfiles(avatars[avatarCopySelected?.id.toString()].profileList.map((profile, index) => {
|
||||
setSelectedProfiles(avatars[avatarCopySelected?.ID.toString()].profileList.map((profile, index) => {
|
||||
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -106,20 +110,20 @@ export default function CopyImport() {
|
||||
return;
|
||||
}
|
||||
|
||||
const newListProfile = avatars[avatarCopySelected.id.toString()].profileList.map((profile) => {
|
||||
const newListProfile = avatars[avatarCopySelected.ID.toString()].profileList.map((profile) => {
|
||||
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...profile,
|
||||
profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.id}`,
|
||||
profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.ID}`,
|
||||
} as AvatarProfileStore
|
||||
}).filter((profile) => profile !== null);
|
||||
|
||||
const newAvatar = {
|
||||
...avatars[avatarSelected.id.toString()],
|
||||
profileList: avatars[avatarSelected.id.toString()].profileList.concat(newListProfile),
|
||||
profileSelect: avatars[avatarSelected.id.toString()].profileList.length - 1,
|
||||
...avatars[avatarSelected?.ID?.toString()],
|
||||
profileList: avatars[avatarSelected?.ID?.toString()].profileList.concat(newListProfile),
|
||||
profileSelect: avatars[avatarSelected?.ID?.toString()].profileList.length - 1,
|
||||
}
|
||||
setAvatar(newAvatar);
|
||||
setSelectedProfiles([]);
|
||||
@@ -143,9 +147,9 @@ export default function CopyImport() {
|
||||
{/* Path */}
|
||||
<div>
|
||||
<div className="flex flex-wrap gap-2 justify-start items-center">
|
||||
{Object.entries(listPath).map(([key], index) => (
|
||||
{Object.entries(baseType).map(([key, value]) => (
|
||||
<div
|
||||
key={index}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
}}
|
||||
@@ -156,7 +160,7 @@ export default function CopyImport() {
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${key}.webp`}
|
||||
src={`${process.env.CDN_URL}/${value.Icon}`}
|
||||
alt={key}
|
||||
className="h-8 w-8 object-contain rounded-md"
|
||||
width={200}
|
||||
@@ -170,9 +174,9 @@ export default function CopyImport() {
|
||||
{/* Element */}
|
||||
<div>
|
||||
<div className="flex flex-wrap gap-2 justify-start items-center">
|
||||
{Object.entries(listElement).map(([key], index) => (
|
||||
{Object.entries(damageType).map(([key, value]) => (
|
||||
<div
|
||||
key={index}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setListElement({ ...listElement, [key]: !listElement[key] })
|
||||
}}
|
||||
@@ -183,7 +187,7 @@ export default function CopyImport() {
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${key}.webp`}
|
||||
src={`${process.env.CDN_URL}/${value.Icon}`}
|
||||
alt={key}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
|
||||
width={200}
|
||||
@@ -217,19 +221,19 @@ export default function CopyImport() {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
|
||||
{listCopyAvatar.length > 0 && (
|
||||
{listAvatar.length > 0 && (
|
||||
<div>
|
||||
<div>{transI18n("characterName")}</div>
|
||||
<SelectCustomImage
|
||||
customSet={listCopyAvatar.map((avatar) => ({
|
||||
value: avatar.id.toString(),
|
||||
customSet={listAvatar.map((avatar) => ({
|
||||
value: avatar.ID.toString(),
|
||||
label: getNameChar(locale, transI18n, avatar),
|
||||
imageUrl: `${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${avatar.id}.png`
|
||||
imageUrl: `${process.env.CDN_URL}/${avatar.Image.AvatarIconPath}`
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={avatarCopySelected?.id.toString() || ""}
|
||||
selectedCustomSet={avatarCopySelected?.ID.toString() || ""}
|
||||
placeholder="Character Select"
|
||||
setSelectedCustomSet={(value) => setAvatarCopySelected(listCopyAvatar.find((avatar) => avatar.id.toString() === value) || null)}
|
||||
setSelectedCustomSet={(value) => setAvatarCopySelected(mapAvatar[value] || null)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -270,7 +274,7 @@ export default function CopyImport() {
|
||||
|
||||
{/* Character Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{avatarCopySelected && avatars[avatarCopySelected?.id.toString()]?.profileList.map((profile, index) => {
|
||||
{avatarCopySelected && avatars[avatarCopySelected?.ID.toString()]?.profileList.map((profile, index) => {
|
||||
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,58 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useMemo } from "react"
|
||||
import Image from "next/image";
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useLightconeStore from "@/stores/lightconeStore";
|
||||
import LightconeCard from "../card/lightconeCard";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import useModelStore from "@/stores/modelStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { calcRarity, getLocaleName } from "@/helper";
|
||||
|
||||
export default function LightconeBar() {
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
listLightcone,
|
||||
filter,
|
||||
setFilter,
|
||||
defaultFilter,
|
||||
listPath,
|
||||
listRank,
|
||||
setListPath,
|
||||
setListRank
|
||||
} = useLightconeStore()
|
||||
avatarSelected,
|
||||
mapLightconePathActive,
|
||||
mapLightconeRankActive,
|
||||
setMapLightconePathActive,
|
||||
setMapLightconeRankActive,
|
||||
lightconeSearch,
|
||||
setLightconeSearch
|
||||
} = useCurrentDataStore()
|
||||
const { setAvatar, avatars } = useUserDataStore()
|
||||
const { avatarSelected } = useAvatarStore()
|
||||
const { setIsOpenLightcone } = useModelStore()
|
||||
const { mapLightCone, baseType } = useDetailDataStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
useEffect(() => {
|
||||
const newListPath: Record<string, boolean> = { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, "elation": false }
|
||||
const newListRank: Record<string, boolean> = { "3": false, "4": false, "5": false }
|
||||
for (const path of defaultFilter.path) {
|
||||
if (path in newListPath) {
|
||||
newListPath[path] = true
|
||||
}
|
||||
}
|
||||
for (const rarity of defaultFilter.rarity) {
|
||||
if (rarity in newListRank) {
|
||||
newListRank[rarity] = true
|
||||
}
|
||||
}
|
||||
setListPath(newListPath)
|
||||
setListRank(newListRank)
|
||||
}, [defaultFilter, setListPath, setListRank])
|
||||
const listLightcone = useMemo(() => {
|
||||
if (!mapLightCone || !locale) return []
|
||||
|
||||
useEffect(() => {
|
||||
setFilter({
|
||||
...filter,
|
||||
locale: locale,
|
||||
path: Object.keys(listPath).filter((key) => listPath[key]),
|
||||
rarity: Object.keys(listRank).filter((key) => listRank[key])
|
||||
let list = Object.values(mapLightCone)
|
||||
|
||||
if (lightconeSearch) {
|
||||
list = list.filter(item => getLocaleName(locale, item.Name).toLowerCase().includes(lightconeSearch.toLowerCase()))
|
||||
}
|
||||
|
||||
const allRankFalse = !Object.values(mapLightconeRankActive).some(v => v)
|
||||
const allPathFalse = !Object.values(mapLightconePathActive).some(v => v)
|
||||
|
||||
list = list.filter(item =>
|
||||
(allRankFalse || mapLightconeRankActive[item.Rarity]) &&
|
||||
(allPathFalse || mapLightconePathActive[item.BaseType])
|
||||
)
|
||||
|
||||
list.sort((a, b) => {
|
||||
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
|
||||
if (r !== 0) return r
|
||||
return a.ID - b.ID
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPath, listRank, locale])
|
||||
|
||||
return list
|
||||
}, [mapLightCone, mapLightconePathActive, mapLightconeRankActive, lightconeSearch, locale])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -65,8 +66,8 @@ export default function LightconeBar() {
|
||||
<div className="flex items-start flex-col gap-2">
|
||||
<div>Search</div>
|
||||
<input
|
||||
value={filter.name}
|
||||
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })}
|
||||
value={lightconeSearch}
|
||||
onChange={(e) => setLightconeSearch(e.target.value)}
|
||||
type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -74,21 +75,21 @@ export default function LightconeBar() {
|
||||
<div>Filter</div>
|
||||
<div className="flex flex-row flex-wrap justify-between mt-1 w-full">
|
||||
<div className="flex flex-wrap mb-1 mx-1 gap-2">
|
||||
{Object.keys(listPath).map((key, index) => (
|
||||
{Object.entries(baseType).map(([key, value]) => (
|
||||
<div
|
||||
key={index}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
setMapLightconePathActive({ ...mapLightconePathActive, [key]: !mapLightconePathActive[key] })
|
||||
}}
|
||||
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
|
||||
backgroundColor: mapLightconePathActive[key] ? "#374151" : "#6B7280"
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${key}.webp`}
|
||||
src={`${process.env.CDN_URL}/${value.Icon}`}
|
||||
alt={key}
|
||||
className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md"
|
||||
width={200}
|
||||
@@ -99,15 +100,15 @@ export default function LightconeBar() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap mb-1 mx-1 gap-2">
|
||||
{Object.keys(listRank).map((key, index) => (
|
||||
{Object.keys(mapLightconeRankActive).map((key, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListRank({ ...listRank, [key]: !listRank[key] })
|
||||
setMapLightconeRankActive({ ...mapLightconeRankActive, [key]: !mapLightconeRankActive[key] })
|
||||
}}
|
||||
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listRank[key] ? "#374151" : "#6B7280"
|
||||
backgroundColor: mapLightconeRankActive[key] ? "#374151" : "#6B7280"
|
||||
}}
|
||||
>
|
||||
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">
|
||||
@@ -124,10 +125,10 @@ export default function LightconeBar() {
|
||||
{listLightcone.map((item, index) => (
|
||||
<div key={index} onClick={() => {
|
||||
if (avatarSelected) {
|
||||
const avatar = avatars[avatarSelected.id]
|
||||
const avatar = avatars[avatarSelected?.ID?.toString()]
|
||||
avatar.profileList[avatar.profileSelect].lightcone = {
|
||||
level: 80,
|
||||
item_id: Number(item.id),
|
||||
item_id: item.ID,
|
||||
rank: 1,
|
||||
promotion: 6
|
||||
}
|
||||
|
||||
@@ -1,60 +1,50 @@
|
||||
"use client"
|
||||
import { useEffect, useMemo } from "react";
|
||||
import SelectCustomText from "../select/customSelectText";
|
||||
import useEventStore from "@/stores/eventStore";
|
||||
import { getLocaleName, replaceByParam } from "@/helper";
|
||||
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import Image from "next/image";
|
||||
import { MonsterStore } from "@/types";
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function AsBar() {
|
||||
const { ASEvent, mapASInfo } = useEventStore()
|
||||
const { mapMonster } = useMonsterStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
as_config,
|
||||
setAsConfig
|
||||
} = useUserDataStore()
|
||||
const { AS } = useMazeStore()
|
||||
const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
|
||||
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
const challengeSelected = useMemo(() => {
|
||||
return mapASInfo[as_config.event_id.toString()]?.Level.find((as) => as.Id === as_config.challenge_id)
|
||||
}, [as_config, mapASInfo])
|
||||
return mapAS[as_config.event_id.toString()]?.Level.find((as) => as.ID === as_config.challenge_id)
|
||||
}, [as_config, mapAS])
|
||||
|
||||
const eventSelected = useMemo(() => {
|
||||
return mapASInfo[as_config.event_id.toString()]
|
||||
}, [as_config, mapASInfo])
|
||||
return mapAS[as_config.event_id.toString()]
|
||||
}, [as_config, mapAS])
|
||||
|
||||
const buffList = useMemo(() => {
|
||||
const challenge = AS[as_config.event_id.toString()];
|
||||
if (!challenge) return { buffList: [], buffId: [] };
|
||||
if (!eventSelected) return [];
|
||||
|
||||
if (as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") {
|
||||
return {
|
||||
buffList: eventSelected?.BuffList1 ?? [],
|
||||
buffId: challenge.buff_1 ?? [],
|
||||
};
|
||||
return eventSelected?.BuffList1 ?? [];
|
||||
}
|
||||
|
||||
if (as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") {
|
||||
return {
|
||||
buffList: eventSelected?.BuffList2 ?? [],
|
||||
buffId: challenge.buff_2 ?? [],
|
||||
};
|
||||
return eventSelected?.BuffList2 ?? [];
|
||||
}
|
||||
return { buffList: [], buffId: [] };
|
||||
}, [AS, as_config.event_id, as_config.floor_side, eventSelected?.BuffList1, eventSelected?.BuffList2]);
|
||||
return [];
|
||||
}, [as_config.floor_side, eventSelected]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return
|
||||
const newBattleConfig = structuredClone(as_config)
|
||||
newBattleConfig.cycle_count = 0
|
||||
newBattleConfig.cycle_count = challengeSelected.TurnLimit
|
||||
|
||||
newBattleConfig.blessings = []
|
||||
if (as_config.buff_id !== 0) {
|
||||
@@ -63,24 +53,27 @@ export default function AsBar() {
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
if (AS[as_config.challenge_id.toString()]) {
|
||||
newBattleConfig.blessings.push({
|
||||
id: Number(AS[as_config.challenge_id.toString()].maze_buff),
|
||||
level: 1
|
||||
|
||||
if (challengeSelected) {
|
||||
challengeSelected.MazeBuff.map((item) => {
|
||||
newBattleConfig.blessings.push({
|
||||
id: item.ID,
|
||||
level: 1
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = 0
|
||||
if ((as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower")
|
||||
&& challengeSelected.EventIDList1.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList1[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList1[0].MonsterList) {
|
||||
&& challengeSelected.EventList1.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList1[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
@@ -88,14 +81,14 @@ export default function AsBar() {
|
||||
}
|
||||
}
|
||||
if ((as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper")
|
||||
&& challengeSelected.EventIDList2.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList2[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList2[0].MonsterList) {
|
||||
&& challengeSelected.EventList2.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList2[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
@@ -103,26 +96,26 @@ export default function AsBar() {
|
||||
}
|
||||
}
|
||||
if (as_config.floor_side === "Lower -> Upper"
|
||||
&& challengeSelected.EventIDList1.length > 0) {
|
||||
for (const wave of challengeSelected.EventIDList1[0].MonsterList) {
|
||||
&& challengeSelected.EventList1.length > 0) {
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList1[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
} else if (as_config.floor_side === "Upper -> Lower"
|
||||
&& challengeSelected.EventIDList2.length > 0) {
|
||||
for (const wave of challengeSelected.EventIDList2[0].MonsterList) {
|
||||
&& challengeSelected.EventList2.length > 0) {
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList2[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
@@ -130,19 +123,17 @@ export default function AsBar() {
|
||||
}
|
||||
}
|
||||
setAsConfig(newBattleConfig)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
challengeSelected,
|
||||
mapAS,
|
||||
as_config.event_id,
|
||||
as_config.challenge_id,
|
||||
as_config.floor_side,
|
||||
as_config.buff_id,
|
||||
mapASInfo,
|
||||
AS,
|
||||
])
|
||||
|
||||
if (!ASEvent) return null
|
||||
if (!mapAS) return null
|
||||
return (
|
||||
<div className="py-8 relative">
|
||||
|
||||
@@ -150,10 +141,10 @@ export default function AsBar() {
|
||||
<div className="rounded-xl p-4 mb-2 border border-warning">
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={ASEvent.map((as) => ({
|
||||
id: as.id,
|
||||
name: getLocaleName(locale, as),
|
||||
time: `${as.begin} - ${as.end}`,
|
||||
customSet={Object.values(mapAS).sort((a, b) => b.ID - a.ID).map((as) => ({
|
||||
id: as.ID.toString(),
|
||||
name: getLocaleName(locale, as.Name),
|
||||
time: `${as.BeginTime} - ${as.EndTime}`,
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={as_config.event_id.toString()}
|
||||
@@ -161,7 +152,7 @@ export default function AsBar() {
|
||||
setSelectedCustomSet={(id) => setAsConfig({
|
||||
...as_config,
|
||||
event_id: Number(id),
|
||||
challenge_id: mapASInfo[Number(id)]?.Level.slice(-1)[0]?.Id || 0,
|
||||
challenge_id: mapAS[Number(id)]?.Level.at(-1)?.ID || 0,
|
||||
buff_id: 0
|
||||
})}
|
||||
/>
|
||||
@@ -179,8 +170,8 @@ export default function AsBar() {
|
||||
onChange={(e) => setAsConfig({ ...as_config, challenge_id: Number(e.target.value) })}
|
||||
>
|
||||
<option value={0} disabled={true}>{transI18n("selectFloor")}</option>
|
||||
{mapASInfo[as_config.event_id.toString()]?.Level.map((as) => (
|
||||
<option key={as.Id} value={as.Id}>{as.Id % 10}</option>
|
||||
{eventSelected?.Level.map((as) => (
|
||||
<option key={as.ID} value={as.ID}>{getLocaleName(locale, as.Name)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -205,15 +196,11 @@ export default function AsBar() {
|
||||
{eventSelected && (
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={
|
||||
Array.isArray(buffList?.buffList) && Array.isArray(buffList?.buffId)
|
||||
? buffList.buffList.map((buff, index) => ({
|
||||
id: buffList.buffId?.[index]?.toString() || "",
|
||||
name: buff?.Name || "",
|
||||
description: replaceByParam(buff?.Desc || "", buff?.Param || []),
|
||||
}))
|
||||
: []
|
||||
}
|
||||
customSet={buffList.map((buff) => ({
|
||||
id: buff.ID?.toString() || "",
|
||||
name: getLocaleName(locale, buff?.Name) || "",
|
||||
description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={as_config?.buff_id?.toString()}
|
||||
placeholder={transI18n("selectBuff")}
|
||||
@@ -224,16 +211,19 @@ export default function AsBar() {
|
||||
{/* Turbulence Buff */}
|
||||
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
|
||||
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2>
|
||||
{eventSelected && eventSelected.Buff?.Name ? (
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
eventSelected.Buff?.Desc || "",
|
||||
eventSelected.Buff?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{challengeSelected ? (
|
||||
challengeSelected.MazeBuff.map((buff, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, buff?.Desc) || "",
|
||||
buff?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="text-base">{transI18n("noTurbulenceBuff")}</div>
|
||||
)}
|
||||
@@ -247,48 +237,85 @@ export default function AsBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.EventIDList1?.length > 0 && challengeSelected?.EventIDList1[0].MonsterList.map((wave, waveIndex) => (
|
||||
{challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[waveValue.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[waveValue.toString()],
|
||||
challengeSelected?.EventList1?.[0]?.EliteGroup,
|
||||
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList1?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList1[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -298,49 +325,85 @@ export default function AsBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.EventIDList2?.length > 0 && challengeSelected?.EventIDList2[0].MonsterList.map((wave, waveIndex) => (
|
||||
{challengeSelected && challengeSelected?.EventList2?.length > 0 && challengeSelected?.EventList2?.[0]?.MonsterList?.map((wave, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[waveValue.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[waveValue.toString()],
|
||||
challengeSelected?.EventList2?.[0]?.EliteGroup,
|
||||
challengeSelected?.EventList2?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList2?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList2[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[waveValue.toString()].weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -9,23 +9,21 @@ import {
|
||||
CopyPlus,
|
||||
} from "lucide-react";
|
||||
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import { getLocaleName } from "@/helper";
|
||||
import Image from "next/image";
|
||||
import { MonsterBasic } from "@/types";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useGlobalStore from "@/stores/globalStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { MonsterDetail } from "@/types";
|
||||
|
||||
|
||||
export default function CeBar() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null);
|
||||
const { Stage } = useMazeStore()
|
||||
const { ce_config, setCeConfig } = useUserDataStore()
|
||||
const { listMonster, mapMonster } = useMonsterStore()
|
||||
const { mapMonster, stage, damageType } = useDetailDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const [showSearchStage, setShowSearchStage] = useState(false)
|
||||
@@ -39,17 +37,17 @@ export default function CeBar() {
|
||||
const [monsterPage, setMonsterPage] = useState(1)
|
||||
|
||||
const filteredMonsters = useMemo(() => {
|
||||
const newlistMonster = new Set<MonsterBasic>()
|
||||
for (const monster of listMonster) {
|
||||
if (getLocaleName(locale, monster).toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
const newlistMonster = new Set<MonsterDetail>()
|
||||
for (const monster of Object.values(mapMonster)) {
|
||||
if (getLocaleName(locale, monster.Name).toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
newlistMonster.add(monster)
|
||||
}
|
||||
if (monster.id.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
if (monster.ID.toString().includes(searchTerm.toLowerCase())) {
|
||||
newlistMonster.add(monster)
|
||||
}
|
||||
}
|
||||
return Array.from(newlistMonster)
|
||||
}, [listMonster, locale, searchTerm]);
|
||||
}, [locale, searchTerm, mapMonster]);
|
||||
|
||||
const paginatedMonsters = useMemo(() =>
|
||||
filteredMonsters.slice((monsterPage - 1) * pageSizeMonsters, monsterPage * pageSizeMonsters),
|
||||
@@ -65,10 +63,10 @@ export default function CeBar() {
|
||||
setMonsterPage(1)
|
||||
}, [searchTerm])
|
||||
|
||||
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
|
||||
id: stage.stage_id.toString(),
|
||||
name: `${stage.stage_type} (${stage.stage_id})`,
|
||||
})), [Stage])
|
||||
const stageList = useMemo(() => Object.values(stage).map((item) => ({
|
||||
id: item.ID.toString(),
|
||||
name: `${getLocaleName(locale, item.Name)} (${item.ID})`,
|
||||
})), [stage, locale])
|
||||
|
||||
const filteredStages = useMemo(() => stageList.filter((s) =>
|
||||
s.name.toLowerCase().includes(stageSearchTerm.toLowerCase())
|
||||
@@ -312,10 +310,10 @@ export default function CeBar() {
|
||||
</button>
|
||||
|
||||
<div className="flex justify-center">
|
||||
{mapMonster?.[member.monster_id.toString()]?.icon && <Image
|
||||
{mapMonster?.[member.monster_id.toString()]?.Image?.IconPath && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[member.monster_id.toString()]?.icon}`}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[member.monster_id.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
@@ -324,11 +322,11 @@ export default function CeBar() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-1 mb-2">
|
||||
{mapMonster?.[member.monster_id.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
{mapMonster?.[member.monster_id.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
@@ -339,7 +337,7 @@ export default function CeBar() {
|
||||
</div>
|
||||
<div className="text-center flex flex-col items-center justify-center">
|
||||
<div className="text-sm font-medium">
|
||||
{getLocaleName(locale, mapMonster?.[member.monster_id.toString()])} {`(${member.monster_id})`}
|
||||
{getLocaleName(locale, mapMonster?.[member.monster_id.toString()]?.Name)} {`(${member.monster_id})`}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 mt-1 mx-2">
|
||||
<span className="text-sm">LV.</span>
|
||||
@@ -444,12 +442,12 @@ export default function CeBar() {
|
||||
{paginatedMonsters.length > 0 ? (
|
||||
paginatedMonsters.map((monster) => (
|
||||
<div
|
||||
key={monster.id}
|
||||
key={monster.ID}
|
||||
className="flex items-center gap-2 p-2 hover:bg-success/40 rounded cursor-pointer"
|
||||
onClick={() => {
|
||||
const newCeConfig = structuredClone(ce_config)
|
||||
newCeConfig.monsters[waveIndex].push({
|
||||
monster_id: Number(monster.id),
|
||||
monster_id: monster.ID,
|
||||
level: 95,
|
||||
amount: 1,
|
||||
})
|
||||
@@ -458,11 +456,11 @@ export default function CeBar() {
|
||||
}}
|
||||
>
|
||||
<div className="relative w-8 h-8 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[monster.id.toString()]?.icon && (
|
||||
{mapMonster?.[monster.ID.toString()]?.Image?.IconPath && (
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monster.id.toString()]?.icon}`}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monster.ID.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
@@ -470,7 +468,7 @@ export default function CeBar() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span>{getLocaleName(locale, monster)} {`(${monster.id})`}</span>
|
||||
<span>{getLocaleName(locale, monster.Name)} {`(${monster.ID})`}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -2,115 +2,117 @@
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import SelectCustomText from "../select/customSelectText";
|
||||
import useEventStore from "@/stores/eventStore";
|
||||
import { getLocaleName, replaceByParam } from "@/helper";
|
||||
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import Image from "next/image";
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { MonsterStore } from "@/types";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function MocBar() {
|
||||
const { MOCEvent, mapMOCInfo } = useEventStore()
|
||||
const { mapMonster } = useMonsterStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
moc_config,
|
||||
setMocConfig
|
||||
} = useUserDataStore()
|
||||
const { MOC } = useMazeStore()
|
||||
const { mapMonster, mapMoc, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
|
||||
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
const challengeSelected = useMemo(() => {
|
||||
return mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id)
|
||||
}, [moc_config, mapMOCInfo])
|
||||
return mapMoc[moc_config.event_id.toString()]?.Level.find((moc) => moc.ID === moc_config.challenge_id)
|
||||
}, [moc_config, mapMoc])
|
||||
|
||||
const eventSelected = useMemo(() => {
|
||||
return mapMoc[moc_config.event_id.toString()]
|
||||
}, [moc_config, mapMoc])
|
||||
|
||||
useEffect(() => {
|
||||
const challenge = mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id)
|
||||
if (moc_config.event_id !== 0 && moc_config.challenge_id !== 0 && challenge) {
|
||||
const newBattleConfig = structuredClone(moc_config)
|
||||
newBattleConfig.cycle_count = 0
|
||||
if (moc_config.use_cycle_count) {
|
||||
newBattleConfig.cycle_count = challenge.Countdown
|
||||
}
|
||||
newBattleConfig.blessings = []
|
||||
if (moc_config.use_turbulence_buff && MOC[moc_config.challenge_id.toString()]) {
|
||||
if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
|
||||
|
||||
const newBattleConfig = structuredClone(moc_config)
|
||||
newBattleConfig.cycle_count = 0
|
||||
if (moc_config.use_cycle_count) {
|
||||
newBattleConfig.cycle_count = challengeSelected.TurnLimit
|
||||
}
|
||||
newBattleConfig.blessings = []
|
||||
if (moc_config.use_turbulence_buff && challengeSelected) {
|
||||
challengeSelected.MazeBuff.map((item) => {
|
||||
newBattleConfig.blessings.push({
|
||||
id: Number(MOC[moc_config.challenge_id.toString()].maze_buff),
|
||||
id: item.ID,
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = 0
|
||||
if ((moc_config.floor_side === "Upper" || moc_config.floor_side === "Upper -> Lower") && challenge.EventIDList1.length > 0) {
|
||||
newBattleConfig.stage_id = challenge.EventIDList1[0].StageID
|
||||
for (const wave of challenge.EventIDList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challenge.EventIDList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if ((moc_config.floor_side === "Lower" || moc_config.floor_side === "Lower -> Upper") && challenge.EventIDList2.length > 0) {
|
||||
newBattleConfig.stage_id = challenge.EventIDList2[0].StageID
|
||||
for (const wave of challenge.EventIDList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challenge.EventIDList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if (moc_config.floor_side === "Lower -> Upper" && challenge.EventIDList1.length > 0) {
|
||||
for (const wave of challenge.EventIDList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challenge.EventIDList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
} else if (moc_config.floor_side === "Upper -> Lower" && challenge.EventIDList2.length > 0) {
|
||||
for (const wave of challenge.EventIDList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challenge.EventIDList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
setMocConfig(newBattleConfig)
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = 0
|
||||
if ((moc_config.floor_side === "Upper" || moc_config.floor_side === "Upper -> Lower") && challengeSelected.EventList1.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if ((moc_config.floor_side === "Lower" || moc_config.floor_side === "Lower -> Upper") && challengeSelected.EventList2.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if (moc_config.floor_side === "Lower -> Upper" && challengeSelected.EventList1.length > 0) {
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
} else if (moc_config.floor_side === "Upper -> Lower" && challengeSelected.EventList2.length > 0) {
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
setMocConfig(newBattleConfig)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
moc_config.event_id,
|
||||
moc_config.challenge_id,
|
||||
moc_config.floor_side,
|
||||
mapMOCInfo,
|
||||
MOC,
|
||||
moc_config.use_cycle_count,
|
||||
moc_config.use_turbulence_buff,
|
||||
mapMoc,
|
||||
])
|
||||
if (!MOCEvent) return null
|
||||
|
||||
if (!mapMoc) return null
|
||||
|
||||
return (
|
||||
<div className="py-8 relative">
|
||||
|
||||
@@ -118,10 +120,10 @@ export default function MocBar() {
|
||||
<div className="rounded-xl p-4 mb-2 border border-warning">
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={MOCEvent.map((moc) => ({
|
||||
id: moc.id,
|
||||
name: getLocaleName(locale, moc),
|
||||
time: `${moc.begin} - ${moc.end}`,
|
||||
customSet={Object.values(mapMoc).sort((a, b) => b.ID - a.ID).map((moc) => ({
|
||||
id: moc.ID.toString(),
|
||||
name: getLocaleName(locale, moc.Name),
|
||||
time: `${moc.BeginTime} - ${moc.EndTime}`,
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={moc_config.event_id.toString()}
|
||||
@@ -129,7 +131,7 @@ export default function MocBar() {
|
||||
setSelectedCustomSet={(id) => setMocConfig({
|
||||
...moc_config,
|
||||
event_id: Number(id),
|
||||
challenge_id: mapMOCInfo[Number(id)]?.slice(-1)[0]?.Id || 0,
|
||||
challenge_id: mapMoc[Number(id)]?.Level.at(-1)?.ID || 0,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
@@ -149,8 +151,8 @@ export default function MocBar() {
|
||||
})}
|
||||
>
|
||||
<option value={0} disabled={true}>Select a Floor</option>
|
||||
{mapMOCInfo[moc_config.event_id.toString()]?.map((moc) => (
|
||||
<option key={moc.Id} value={moc.Id}>{moc.Id % 100}</option>
|
||||
{eventSelected?.Level?.map((moc) => (
|
||||
<option key={moc.ID} value={moc.ID}>{getLocaleName(locale, moc.Name)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -205,16 +207,19 @@ export default function MocBar() {
|
||||
{transI18n("useTurbulenceBuff")}
|
||||
</span>
|
||||
</div>
|
||||
{challengeSelected && challengeSelected?.Desc ? (
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
challengeSelected?.Desc || "",
|
||||
challengeSelected?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{challengeSelected ? (
|
||||
challengeSelected.MazeBuff.map((buff, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, buff?.Desc) || "",
|
||||
buff?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="text-base">{transI18n("noTurbulenceBuff")}</div>
|
||||
)}
|
||||
@@ -228,48 +233,85 @@ export default function MocBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.EventIDList1?.length > 0 && challengeSelected?.EventIDList1[0].MonsterList.map((wave, waveIndex) => (
|
||||
{challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[waveValue.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[waveValue.toString()],
|
||||
challengeSelected?.EventList1?.[0]?.EliteGroup,
|
||||
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList1?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList1[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -279,49 +321,85 @@ export default function MocBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.EventIDList2?.length > 0 && challengeSelected?.EventIDList2[0].MonsterList.map((wave, waveIndex) => (
|
||||
{challengeSelected && challengeSelected?.EventList2?.length > 0 && challengeSelected?.EventList2?.[0]?.MonsterList?.map((wave, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[waveValue.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Object.values(wave).map((waveValue, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[waveValue.toString()],
|
||||
challengeSelected?.EventList2?.[0]?.EliteGroup,
|
||||
challengeSelected?.EventList2?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList2?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList2[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,57 +1,49 @@
|
||||
"use client"
|
||||
import { useEffect, useMemo } from "react";
|
||||
import SelectCustomText from "../select/customSelectText";
|
||||
import useEventStore from "@/stores/eventStore";
|
||||
import { getLocaleName, replaceByParam } from "@/helper";
|
||||
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";;
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { MonsterStore } from "@/types";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function PeakBar() {
|
||||
const { PEAKEvent, mapPEAKInfo } = useEventStore()
|
||||
const { mapMonster } = useMonsterStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
peak_config,
|
||||
setPeakConfig
|
||||
} = useUserDataStore()
|
||||
|
||||
const { mapMonster, mapPeak, damageType, eliteConfig, hardLevelConfig } = useDetailDataStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
const listFloor = useMemo(() => {
|
||||
if (!mapPEAKInfo?.[peak_config?.event_id?.toString()]) return []
|
||||
return [
|
||||
...mapPEAKInfo[peak_config?.event_id?.toString()]?.PreLevel,
|
||||
mapPEAKInfo[peak_config?.event_id?.toString()]?.BossLevel,
|
||||
]
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
const peak = mapPeak?.[peak_config?.event_id?.toString()]
|
||||
if (!peak) return []
|
||||
|
||||
return [...peak.PreLevel, peak.BossLevel].filter(it => it != null)
|
||||
}, [peak_config, mapPeak])
|
||||
const eventSelected = useMemo(() => {
|
||||
return mapPEAKInfo?.[peak_config?.event_id?.toString()]
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
return mapPeak?.[peak_config?.event_id?.toString()]
|
||||
}, [peak_config, mapPeak])
|
||||
|
||||
const bossConfig = useMemo(() => {
|
||||
return mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossConfig;
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
return mapPeak?.[peak_config?.event_id?.toString()]?.BossConfig;
|
||||
}, [peak_config, mapPeak])
|
||||
|
||||
const challengeSelected = useMemo(() => {
|
||||
const challenge = structuredClone(listFloor.find((peak) => peak.Id === peak_config.challenge_id))
|
||||
const challenge = structuredClone(listFloor?.find((peak) => peak?.ID === peak_config.challenge_id))
|
||||
if (
|
||||
challenge
|
||||
&& challenge.Id === mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossLevel?.Id
|
||||
&& challenge.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID
|
||||
&& bossConfig
|
||||
&& peak_config?.boss_mode === "Hard"
|
||||
) {
|
||||
challenge.Name = bossConfig.HardName
|
||||
challenge.EventIDList = bossConfig.EventIDList
|
||||
challenge.InfiniteList = bossConfig.InfiniteList
|
||||
challenge.TagList = bossConfig.TagList
|
||||
return bossConfig
|
||||
}
|
||||
return challenge
|
||||
}, [peak_config, listFloor, mapPEAKInfo, bossConfig])
|
||||
}, [peak_config, listFloor, mapPeak, bossConfig])
|
||||
|
||||
useEffect(() => {
|
||||
if (!challengeSelected) return
|
||||
@@ -59,9 +51,9 @@ export default function PeakBar() {
|
||||
const newBattleConfig = structuredClone(peak_config)
|
||||
newBattleConfig.cycle_count = 6
|
||||
newBattleConfig.blessings = []
|
||||
for (const value of challengeSelected.TagList) {
|
||||
for (const value of challengeSelected.MazeBuff) {
|
||||
newBattleConfig.blessings.push({
|
||||
id: Number(value.Id),
|
||||
id: value.ID,
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
@@ -72,15 +64,15 @@ export default function PeakBar() {
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList[0].MonsterList) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList[0].ID
|
||||
for (const wave of challengeSelected.EventList[0].MonsterList) {
|
||||
if (!wave) continue
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
if (!value) continue
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
@@ -94,10 +86,11 @@ export default function PeakBar() {
|
||||
peak_config.event_id,
|
||||
peak_config.challenge_id,
|
||||
peak_config.buff_id,
|
||||
mapPEAKInfo,
|
||||
peak_config.boss_mode,
|
||||
mapPeak,
|
||||
])
|
||||
|
||||
if (!PEAKEvent) return null
|
||||
if (!mapPeak) return null
|
||||
|
||||
return (
|
||||
<div className="py-8 relative">
|
||||
@@ -106,9 +99,9 @@ export default function PeakBar() {
|
||||
<div className="rounded-xl p-4 mb-2 border border-warning">
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={PEAKEvent.map((peak) => ({
|
||||
id: peak.id,
|
||||
name: `${getLocaleName(locale, peak)} (${peak.id})`,
|
||||
customSet={Object.values(mapPeak).sort((a, b) => b.ID - a.ID).map((peak) => ({
|
||||
id: peak.ID.toString(),
|
||||
name: `${getLocaleName(locale, peak.Name)} (${peak.ID})`,
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={peak_config.event_id.toString()}
|
||||
@@ -119,7 +112,7 @@ export default function PeakBar() {
|
||||
{/* Settings */}
|
||||
<div className={
|
||||
`grid grid-cols-1
|
||||
${eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id ? "md:grid-cols-2" : ""}
|
||||
${eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id ? "md:grid-cols-2" : ""}
|
||||
gap-4 mb-4 justify-items-center items-center w-full`}
|
||||
>
|
||||
|
||||
@@ -134,11 +127,11 @@ export default function PeakBar() {
|
||||
>
|
||||
<option value={0} disabled={true}>{transI18n("selectFloor")}</option>
|
||||
{listFloor.map((peak) => (
|
||||
<option key={peak.Id} value={peak.Id}>{peak.Name}</option>
|
||||
<option key={peak.ID} value={peak.ID}>{getLocaleName(locale, peak.Name)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id && (
|
||||
{eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id && (
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span>
|
||||
@@ -158,20 +151,17 @@ export default function PeakBar() {
|
||||
</div>
|
||||
{
|
||||
eventSelected
|
||||
&& eventSelected.BossLevel.Id === peak_config.challenge_id
|
||||
&& eventSelected.BossLevel?.ID === peak_config.challenge_id
|
||||
&& bossConfig
|
||||
&& bossConfig.BuffList
|
||||
&& (
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={
|
||||
Array.isArray(bossConfig.BuffList)
|
||||
? bossConfig.BuffList.map((buff) => ({
|
||||
id: buff.Id.toString(),
|
||||
name: buff?.Name || "",
|
||||
description: replaceByParam(buff?.Desc || "", buff?.Param || []),
|
||||
}))
|
||||
: []
|
||||
bossConfig.BuffList.map((buff) => ({
|
||||
id: buff.ID.toString(),
|
||||
name: getLocaleName(locale, buff?.Name || ""),
|
||||
description: replaceByParam(getLocaleName(locale, buff?.Desc || ""), buff?.Param || []),
|
||||
}))
|
||||
}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={peak_config?.buff_id?.toString()}
|
||||
@@ -189,19 +179,19 @@ export default function PeakBar() {
|
||||
{transI18n("turbulenceBuff")}
|
||||
</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.TagList?.length > 0 ? (
|
||||
challengeSelected.TagList.map((subOption, index) => (
|
||||
{challengeSelected && challengeSelected?.MazeBuff?.length > 0 ? (
|
||||
challengeSelected.MazeBuff.map((subOption, index) => (
|
||||
<div key={index}>
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">
|
||||
{index + 1}. {subOption.Name}
|
||||
{index + 1}. {getLocaleName(locale, subOption.Name)}
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
subOption.Desc,
|
||||
getLocaleName(locale, subOption.Desc),
|
||||
subOption.Param || []
|
||||
)
|
||||
}}
|
||||
@@ -220,51 +210,87 @@ export default function PeakBar() {
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{challengeSelected?.Name}</h2>
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{getLocaleName(locale, challengeSelected?.Name)}</h2>
|
||||
|
||||
{challengeSelected && Object.values(challengeSelected.InfiniteList).map((waveValue, waveIndex) => (
|
||||
{challengeSelected && Object.values(challengeSelected?.EventList?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[monsterId.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[monsterId.toString()],
|
||||
waveValue.EliteGroup,
|
||||
challengeSelected?.EventList?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
"use client"
|
||||
import { useEffect, useMemo } from "react";
|
||||
import SelectCustomText from "../select/customSelectText";
|
||||
import useEventStore from "@/stores/eventStore";
|
||||
import { getLocaleName, replaceByParam } from "@/helper";
|
||||
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import Image from "next/image";
|
||||
import { MonsterStore } from "@/types";
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
|
||||
export default function PfBar() {
|
||||
const { PFEvent, mapPFInfo } = useEventStore()
|
||||
const { mapMonster } = useMonsterStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
pf_config,
|
||||
setPfConfig
|
||||
} = useUserDataStore()
|
||||
const { PF } = useMazeStore()
|
||||
const { mapMonster, mapPF, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
|
||||
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const challengeSelected = useMemo(() => {
|
||||
return mapPFInfo[pf_config.event_id.toString()]?.Level.find((pf) => pf.Id === pf_config.challenge_id)
|
||||
}, [pf_config, mapPFInfo])
|
||||
return mapPF[pf_config.event_id.toString()]?.Level.find((pf) => pf.ID === pf_config.challenge_id)
|
||||
}, [pf_config, mapPF])
|
||||
|
||||
const eventSelected = useMemo(() => {
|
||||
return mapPFInfo[pf_config.event_id.toString()]
|
||||
}, [pf_config, mapPFInfo])
|
||||
return mapPF[pf_config.event_id.toString()]
|
||||
}, [pf_config, mapPF])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) {
|
||||
return
|
||||
}
|
||||
const newBattleConfig = structuredClone(pf_config)
|
||||
newBattleConfig.cycle_count = 4
|
||||
newBattleConfig.cycle_count = challengeSelected.TurnLimit
|
||||
newBattleConfig.blessings = []
|
||||
if (pf_config.buff_id !== 0) {
|
||||
newBattleConfig.blessings.push({
|
||||
@@ -43,62 +40,63 @@ export default function PfBar() {
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
if (PF[pf_config.challenge_id.toString()]) {
|
||||
newBattleConfig.blessings.push({
|
||||
id: Number(PF[pf_config.challenge_id.toString()].maze_buff),
|
||||
level: 1
|
||||
if (challengeSelected) {
|
||||
challengeSelected.MazeBuff.map((item) => {
|
||||
newBattleConfig.blessings.push({
|
||||
id: item.ID,
|
||||
level: 1
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = 0
|
||||
if ((pf_config.floor_side === "Upper" || pf_config.floor_side === "Upper -> Lower") && challengeSelected.EventIDList1.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList1[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList1[0].MonsterList) {
|
||||
if ((pf_config.floor_side === "Upper" || pf_config.floor_side === "Upper -> Lower") && challengeSelected.EventList1.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList1[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if ((pf_config.floor_side === "Lower" || pf_config.floor_side === "Lower -> Upper") && challengeSelected.EventIDList2.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList2[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList2[0].MonsterList) {
|
||||
if ((pf_config.floor_side === "Lower" || pf_config.floor_side === "Lower -> Upper") && challengeSelected.EventList2.length > 0) {
|
||||
newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList2[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
}
|
||||
if (pf_config.floor_side === "Lower -> Upper" && challengeSelected.EventIDList1.length > 0) {
|
||||
for (const wave of challengeSelected.EventIDList1[0].MonsterList) {
|
||||
if (pf_config.floor_side === "Lower -> Upper" && challengeSelected.EventList1.length > 0) {
|
||||
for (const wave of challengeSelected.EventList1[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList1[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList1[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
} else if (pf_config.floor_side === "Upper -> Lower" && challengeSelected.EventIDList2.length > 0) {
|
||||
for (const wave of challengeSelected.EventIDList2[0].MonsterList) {
|
||||
} else if (pf_config.floor_side === "Upper -> Lower" && challengeSelected.EventList2.length > 0) {
|
||||
for (const wave of challengeSelected.EventList2[0].MonsterList) {
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList2[0].Level,
|
||||
monster_id: value,
|
||||
level: challengeSelected.EventList2[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
@@ -113,10 +111,9 @@ export default function PfBar() {
|
||||
pf_config.challenge_id,
|
||||
pf_config.floor_side,
|
||||
pf_config.buff_id,
|
||||
mapPFInfo,
|
||||
PF,
|
||||
mapPF,
|
||||
])
|
||||
if (!PFEvent) return null
|
||||
if (!mapPF) return null
|
||||
|
||||
return (
|
||||
<div className="py-8 relative">
|
||||
@@ -125,10 +122,10 @@ export default function PfBar() {
|
||||
<div className="rounded-xl p-4 mb-2 border border-warning">
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={PFEvent.map((pf) => ({
|
||||
id: pf.id,
|
||||
name: getLocaleName(locale, pf),
|
||||
time: `${pf.begin} - ${pf.end}`,
|
||||
customSet={Object.values(mapPF).sort((a, b) => b.ID - a.ID).map((pf) => ({
|
||||
id: pf.ID.toString(),
|
||||
name: getLocaleName(locale, pf.Name),
|
||||
time: `${pf.BeginTime} - ${pf.EndTime}`,
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={pf_config.event_id.toString()}
|
||||
@@ -136,7 +133,7 @@ export default function PfBar() {
|
||||
setSelectedCustomSet={(id) => setPfConfig({
|
||||
...pf_config,
|
||||
event_id: Number(id),
|
||||
challenge_id: mapPFInfo[Number(id)]?.Level.slice(-1)[0]?.Id || 0,
|
||||
challenge_id: mapPF[Number(id)]?.Level.slice(-1)[0]?.ID || 0,
|
||||
buff_id: 0
|
||||
})}
|
||||
/>
|
||||
@@ -154,8 +151,8 @@ export default function PfBar() {
|
||||
onChange={(e) => setPfConfig({ ...pf_config, challenge_id: Number(e.target.value) })}
|
||||
>
|
||||
<option value={0} disabled={true}>{transI18n("selectFloor")}</option>
|
||||
{mapPFInfo[pf_config.event_id.toString()]?.Level.map((pf) => (
|
||||
<option key={pf.Id} value={pf.Id}>{pf.Id % 10}</option>
|
||||
{eventSelected?.Level.map((pf) => (
|
||||
<option key={pf.ID} value={pf.ID}>{getLocaleName(locale, pf.Name)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -180,10 +177,10 @@ export default function PfBar() {
|
||||
{eventSelected && (
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={eventSelected.Option.map((buff, index) => ({
|
||||
id: PF[eventSelected.Id.toString()]?.buff[index]?.toString(),
|
||||
name: buff.Name,
|
||||
description: replaceByParam(buff.Desc, buff.Param || []),
|
||||
customSet={eventSelected.Option.map((buff) => ({
|
||||
id: buff.ID?.toString() || "",
|
||||
name: getLocaleName(locale, buff?.Name) || "",
|
||||
description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={pf_config?.buff_id?.toString()}
|
||||
@@ -199,29 +196,32 @@ export default function PfBar() {
|
||||
eventSelected.SubOption.map((subOption, index) => (
|
||||
<div key={index}>
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">{index + 1}. {subOption.Name}</span>
|
||||
<span className="label-text font-bold text-success">{index + 1}. {getLocaleName(locale, subOption.Name)}</span>
|
||||
</label>
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
subOption.Desc,
|
||||
getLocaleName(locale, subOption.Desc) || "",
|
||||
subOption.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : eventSelected && eventSelected.SubOption.length === 0 ? (
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
eventSelected.Buff?.Desc || "",
|
||||
eventSelected.Buff?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
) : eventSelected && challengeSelected && eventSelected.SubOption.length === 0 ? (
|
||||
challengeSelected?.MazeBuff?.map((buff, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
getLocaleName(locale, buff?.Desc) || "",
|
||||
buff?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="text-base">{transI18n("noTurbulenceBuff")}</div>
|
||||
)}
|
||||
@@ -236,49 +236,85 @@ export default function PfBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && Object.values(challengeSelected.InfiniteList1).map((waveValue, waveIndex) => (
|
||||
{challengeSelected && Object.values(challengeSelected.EventList1?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => (
|
||||
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[monsterId.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[monsterId.toString()],
|
||||
waveValue.EliteGroup,
|
||||
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList1?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList1[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">{mapMonster?.[monsterId.toString()]?.id} | Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -288,49 +324,85 @@ export default function PfBar() {
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
|
||||
|
||||
{challengeSelected && Object.values(challengeSelected?.InfiniteList2).map((waveValue, waveIndex) => (
|
||||
{challengeSelected && Object.values(challengeSelected?.EventList2[0]?.Infinite || []).map((waveValue, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{mapMonster?.[monsterId.toString()]?.icon && <Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`}
|
||||
alt="Enemy Icon"
|
||||
width={400}
|
||||
height={300}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
|
||||
const monsterStats = calcMonsterStats(
|
||||
mapMonster?.[monsterId.toString()],
|
||||
waveValue.EliteGroup,
|
||||
challengeSelected?.EventList2?.[0]?.HardLevelGroup,
|
||||
challengeSelected?.EventList2?.[0]?.Level,
|
||||
hardLevelConfig,
|
||||
eliteConfig
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
|
||||
>
|
||||
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
|
||||
Lv. {challengeSelected?.EventList2[0].Level}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
|
||||
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
|
||||
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
|
||||
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
|
||||
alt="Enemy Icon"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col px-1 pb-2 pt-2">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-error">HP</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-info">Speed</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
|
||||
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
|
||||
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
|
||||
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
|
||||
Weakness
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-1.5 flex-wrap">
|
||||
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
key={iconIndex}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
|
||||
alt={icon}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
"use client"
|
||||
import NextImage from "next/image"
|
||||
import useLightconeStore from "@/stores/lightconeStore";
|
||||
import useAffixStore from "@/stores/affixStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useRelicStore from "@/stores/relicStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useMemo } from "react";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, replaceByParam } from "@/helper";
|
||||
import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, getLocaleName, replaceByParam } from "@/helper";
|
||||
import { mappingStats } from "@/constant/constant";
|
||||
import RelicShowcase from "../showcaseCard/relicShowcase";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
|
||||
export default function QuickView() {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
|
||||
const { mapLightconeInfo } = useLightconeStore()
|
||||
const { mapMainAffix, mapSubAffix } = useAffixStore()
|
||||
const { avatars } = useUserDataStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { mapRelicInfo } = useRelicStore()
|
||||
|
||||
const avatarInfo = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return mapAvatarInfo[avatarSelected.id]
|
||||
}, [avatarSelected, mapAvatarInfo])
|
||||
const { locale } = useLocaleStore()
|
||||
const { avatarSelected, } = useCurrentDataStore()
|
||||
const { mainAffix, subAffix, mapRelicSet, mapLightCone, mapAvatar } = useDetailDataStore()
|
||||
|
||||
|
||||
const avatarSkillTree = useMemo(() => {
|
||||
if (!avatarSelected || !avatars[avatarSelected.id]) return {}
|
||||
if (avatars[avatarSelected.id].enhanced) {
|
||||
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {}
|
||||
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
|
||||
if (avatars[avatarSelected?.ID?.toString()].enhanced) {
|
||||
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
|
||||
}
|
||||
return avatarInfo?.SkillTrees || {}
|
||||
}, [avatarSelected, avatarInfo, avatars])
|
||||
return avatarSelected?.SkillTrees || {}
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarData = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return avatars[avatarSelected.id]
|
||||
return avatars[avatarSelected?.ID?.toString()]
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarProfile = useMemo(() => {
|
||||
@@ -42,7 +37,7 @@ export default function QuickView() {
|
||||
}, [avatarSelected, avatarData])
|
||||
|
||||
const relicEffects = useMemo(() => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const avatar = avatars[avatarSelected?.ID?.toString() || ""];
|
||||
const relicCount: { [key: string]: number } = {};
|
||||
if (avatar) {
|
||||
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
|
||||
@@ -63,32 +58,32 @@ export default function QuickView() {
|
||||
}, [avatars, avatarSelected]);
|
||||
|
||||
const relicStats = useMemo(() => {
|
||||
if (!avatarSelected || !avatarProfile?.relics || !mapMainAffix || !mapSubAffix) return
|
||||
if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return
|
||||
|
||||
return Object.entries(avatarProfile?.relics).map(([key, value]) => {
|
||||
const mainAffixMap = mapMainAffix["5" + key]
|
||||
const subAffixMap = mapSubAffix["5"]
|
||||
const mainAffixMap = mainAffix["5" + key]
|
||||
const subAffixMap = subAffix["5"]
|
||||
if (!mainAffixMap || !subAffixMap) return
|
||||
return {
|
||||
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
|
||||
mainAffix: {
|
||||
property: mainAffixMap?.[value?.main_affix_id]?.property,
|
||||
property: mainAffixMap?.[value?.main_affix_id]?.Property,
|
||||
level: value?.level,
|
||||
valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level),
|
||||
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.property]
|
||||
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property]
|
||||
},
|
||||
subAffix: value?.sub_affixes?.map((subValue) => {
|
||||
return {
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.property,
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.Property,
|
||||
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property],
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property],
|
||||
step: subValue?.step,
|
||||
count: subValue?.count
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [avatarSelected, avatarProfile, mapMainAffix, mapSubAffix])
|
||||
}, [avatarSelected, avatarProfile, mainAffix, subAffix])
|
||||
|
||||
const characterStats = useMemo(() => {
|
||||
if (!avatarSelected || !avatarData) return
|
||||
@@ -104,73 +99,73 @@ export default function QuickView() {
|
||||
}> = {
|
||||
HP: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "HP",
|
||||
icon: "/icon/hp.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
ATK: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "ATK",
|
||||
icon: "/icon/attack.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconAttack.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
DEF: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "DEF",
|
||||
icon: "/icon/defence.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconDefence.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
SPD: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
name: "SPD",
|
||||
icon: "/icon/speed.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconSpeed.png",
|
||||
unit: "",
|
||||
round: 1
|
||||
},
|
||||
CRITRate: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
name: "CRIT Rate",
|
||||
icon: "/icon/crit-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
CRITDmg: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
name: "CRIT DMG",
|
||||
icon: "/icon/crit-damage.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -178,7 +173,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Break Effect",
|
||||
icon: "/icon/break-effect.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -186,7 +181,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Effect RES",
|
||||
icon: "/icon/effect-res.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -194,7 +189,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Energy Rate",
|
||||
icon: "/icon/energy-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -202,7 +197,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Effect Hit Rate",
|
||||
icon: "/icon/effect-hit-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -210,7 +205,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Healing Boost",
|
||||
icon: "/icon/healing-boost.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -218,7 +213,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Physical Boost",
|
||||
icon: "/icon/physical-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -226,7 +221,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Fire Boost",
|
||||
icon: "/icon/fire-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -234,7 +229,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Ice Boost",
|
||||
icon: "/icon/ice-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -242,7 +237,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Thunder Boost",
|
||||
icon: "/icon/thunder-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -250,7 +245,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Wind Boost",
|
||||
icon: "/icon/wind-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -258,7 +253,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Quantum Boost",
|
||||
icon: "/icon/quantum-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -266,7 +261,7 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Imaginary Boost",
|
||||
icon: "/icon/imaginary-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -274,57 +269,57 @@ export default function QuickView() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Elation Boost",
|
||||
icon: "/icon/IconJoy.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconJoy.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) {
|
||||
if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
|
||||
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
|
||||
statsData.HP.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.HP.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.ATK.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.ATK.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.DEF.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.DEF.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
|
||||
const bonusData = mapLightconeInfo[avatarProfile?.lightcone?.item_id].Bonus?.[avatarProfile?.lightcone.rank - 1]
|
||||
const bonusData = mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus
|
||||
if (bonusData && bonusData.length > 0) {
|
||||
const bonusSpd = bonusData.filter((bonus) => bonus.type === "BaseSpeed")
|
||||
const bonusOther = bonusData.filter((bonus) => bonus.type !== "BaseSpeed")
|
||||
const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed")
|
||||
const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed")
|
||||
bonusSpd.forEach((bonus) => {
|
||||
statsData.SPD.value += bonus.value
|
||||
statsData.SPD.base += bonus.value
|
||||
statsData.SPD.value += bonus.Value
|
||||
statsData.SPD.base += bonus.Value
|
||||
})
|
||||
bonusOther.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.type]?.baseStat
|
||||
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
|
||||
if (statsBase && statsData[statsBase]) {
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value)
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -349,17 +344,17 @@ export default function QuickView() {
|
||||
|
||||
|
||||
|
||||
if (avatarProfile?.relics && mapMainAffix && mapSubAffix) {
|
||||
if (avatarProfile?.relics && mainAffix && subAffix) {
|
||||
Object.entries(avatarProfile?.relics).forEach(([key, value]) => {
|
||||
const mainAffixMap = mapMainAffix["5" + key]
|
||||
const subAffixMap = mapSubAffix["5"]
|
||||
const mainAffixMap = mainAffix["5" + key]
|
||||
const subAffixMap = subAffix["5"]
|
||||
if (!mainAffixMap || !subAffixMap) return
|
||||
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.property]?.baseStat
|
||||
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat
|
||||
if (mainStats && statsData[mainStats]) {
|
||||
statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base)
|
||||
}
|
||||
value?.sub_affixes.forEach((subValue) => {
|
||||
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.property]?.baseStat
|
||||
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat
|
||||
if (subStats && statsData[subStats]) {
|
||||
statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base)
|
||||
}
|
||||
@@ -369,14 +364,14 @@ export default function QuickView() {
|
||||
|
||||
if (relicEffects && relicEffects.length > 0) {
|
||||
relicEffects.forEach((relic) => {
|
||||
const dataBonus = mapRelicInfo?.[relic.key]?.Bonus
|
||||
const dataBonus = mapRelicSet?.[relic.key]?.Skills
|
||||
if (!dataBonus || Object.keys(dataBonus).length === 0) return
|
||||
Object.entries(dataBonus || {}).forEach(([key, value]) => {
|
||||
if (relic.count < Number(key)) return
|
||||
value.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.type]?.baseStat
|
||||
value.Bonus.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
|
||||
if (statsBase && statsData[statsBase]) {
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value)
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -388,14 +383,14 @@ export default function QuickView() {
|
||||
}, [
|
||||
avatarSelected,
|
||||
avatarData,
|
||||
mapAvatarInfo,
|
||||
mapAvatar,
|
||||
avatarProfile?.lightcone,
|
||||
avatarProfile?.relics,
|
||||
mapLightconeInfo,
|
||||
mapMainAffix,
|
||||
mapSubAffix,
|
||||
mapLightCone,
|
||||
mainAffix,
|
||||
subAffix,
|
||||
relicEffects,
|
||||
mapRelicInfo,
|
||||
mapRelicSet,
|
||||
avatarSkillTree
|
||||
])
|
||||
|
||||
@@ -409,13 +404,13 @@ export default function QuickView() {
|
||||
<div key={index} className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<NextImage
|
||||
src={stat?.icon || ""}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt="Stat Icon"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-auto w-10 p-1 mx-1 bg-black/20 rounded-full"
|
||||
src={stat?.icon || ""}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt="Stat Icon"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-10 w-10 p-1 mx-1 bg-black/20 rounded-full"
|
||||
/>
|
||||
<div className="font-bold">{stat.name}</div>
|
||||
</div>
|
||||
@@ -431,7 +426,7 @@ export default function QuickView() {
|
||||
|
||||
<div className="flex flex-col items-center gap-1 w-full my-2">
|
||||
{relicEffects.map((setEffect, index) => {
|
||||
const relicInfo = mapRelicInfo[setEffect.key];
|
||||
const relicInfo = mapRelicSet[setEffect.key];
|
||||
if (!relicInfo) return null;
|
||||
return (
|
||||
<div key={index} className="flex w-full flex-row justify-between text-left">
|
||||
@@ -442,7 +437,7 @@ export default function QuickView() {
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
relicInfo.Name,
|
||||
getLocaleName(locale, relicInfo.Name),
|
||||
[]
|
||||
)
|
||||
}}
|
||||
@@ -459,9 +454,9 @@ export default function QuickView() {
|
||||
<div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg">
|
||||
|
||||
{relicStats?.map((relic, index) => {
|
||||
if (!relic || !avatarInfo) return null
|
||||
if (!relic || !avatarSelected) return null
|
||||
return (
|
||||
<RelicShowcase key={index} relic={relic} avatarInfo={avatarInfo} />
|
||||
<RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} />
|
||||
)
|
||||
})}
|
||||
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
"use client";
|
||||
import useRelicStore from '@/stores/relicStore';
|
||||
import useUserDataStore from '@/stores/userDataStore';
|
||||
import { AffixDetail, RelicDetail } from '@/types';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import SelectCustomImage from '../select/customSelectImage';
|
||||
import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam } from '@/helper';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam, getLocaleName } from '@/helper';
|
||||
import { mappingStats } from '@/constant/constant';
|
||||
import useAvatarStore from '@/stores/avatarStore';
|
||||
import useModelStore from '@/stores/modelStore';
|
||||
import useRelicMakerStore from '@/stores/relicMakerStore';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
import useCurrentDataStore from '@/stores/currentDataStore';
|
||||
import { RelicSetDetail, SubAffixData } from '@/types';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { mappingRelicSlot } from "@/constant/constant";
|
||||
|
||||
export default function RelicMaker() {
|
||||
const { avatars, setAvatars } = useUserDataStore()
|
||||
const { avatarSelected } = useAvatarStore()
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { setIsOpenRelic } = useModelStore()
|
||||
const { mapRelicInfo } = useRelicStore()
|
||||
const { mapMainAffix, mapSubAffix } = useAffixStore()
|
||||
const { mainAffix, subAffix, mapRelicSet } = useDetailDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const {
|
||||
selectedRelicSlot,
|
||||
@@ -40,11 +41,11 @@ export default function RelicMaker() {
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const relicSets = useMemo(() => {
|
||||
const listSet: Record<string, RelicDetail> = {};
|
||||
for (const [key, value] of Object.entries(mapRelicInfo || {})) {
|
||||
const listSet: Record<string, RelicSetDetail> = {};
|
||||
for (const [key, value] of Object.entries(mapRelicSet || {})) {
|
||||
let isOk = false;
|
||||
for (const key2 of Object.keys(value.Parts)) {
|
||||
if (key2.endsWith(selectedRelicSlot)) {
|
||||
if (key2 == mappingRelicSlot?.[selectedRelicSlot]) {
|
||||
isOk = true;
|
||||
break;
|
||||
}
|
||||
@@ -54,30 +55,30 @@ export default function RelicMaker() {
|
||||
}
|
||||
}
|
||||
return listSet;
|
||||
}, [mapRelicInfo, selectedRelicSlot]);
|
||||
}, [mapRelicSet , selectedRelicSlot]);
|
||||
|
||||
const subAffixOptions = useMemo(() => {
|
||||
const listSet: Record<string, AffixDetail> = {};
|
||||
const subAffixMap = mapSubAffix["5"];
|
||||
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot]
|
||||
const listSet: Record<string, SubAffixData> = {};
|
||||
const subAffixMap = subAffix["5"];
|
||||
const mainAffixMap = mainAffix["5" + selectedRelicSlot]
|
||||
|
||||
if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet;
|
||||
|
||||
for (const [key, value] of Object.entries(subAffixMap)) {
|
||||
if (value.property !== mainAffixMap[selectedMainStat]?.property) {
|
||||
if (value.Property !== mainAffixMap[selectedMainStat]?.Property) {
|
||||
listSet[key] = value;
|
||||
}
|
||||
}
|
||||
return listSet;
|
||||
}, [mapSubAffix, mapMainAffix, selectedRelicSlot, selectedMainStat]);
|
||||
}, [subAffix, mainAffix, selectedRelicSlot, selectedMainStat]);
|
||||
|
||||
useEffect(() => {
|
||||
const subAffixMap = mapSubAffix["5"];
|
||||
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot];
|
||||
const subAffixMap = subAffix["5"];
|
||||
const mainAffixMap = mainAffix["5" + selectedRelicSlot];
|
||||
|
||||
if (!subAffixMap || !mainAffixMap) return;
|
||||
|
||||
const mainProp = mainAffixMap[selectedMainStat]?.property;
|
||||
const mainProp = mainAffixMap[selectedMainStat]?.Property;
|
||||
if (!mainProp) return;
|
||||
|
||||
const newSubAffixes = structuredClone(listSelectedSubStats);
|
||||
@@ -95,33 +96,33 @@ export default function RelicMaker() {
|
||||
|
||||
if (updated) setListSelectedSubStats(newSubAffixes);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedMainStat, mapSubAffix, mapMainAffix, selectedRelicSlot]);
|
||||
}, [selectedMainStat, subAffix, mainAffix, selectedRelicSlot]);
|
||||
|
||||
const exSubAffixOptions = useMemo(() => {
|
||||
const listSet: Record<string, AffixDetail> = {};
|
||||
const subAffixMap = mapSubAffix["5"];
|
||||
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot];
|
||||
const listSet: Record<string, SubAffixData> = {};
|
||||
const subAffixMap = subAffix["5"];
|
||||
const mainAffixMap = mainAffix["5" + selectedRelicSlot];
|
||||
|
||||
if (!subAffixMap || !mainAffixMap) return listSet;
|
||||
|
||||
for (const [key, value] of Object.entries(subAffixMap)) {
|
||||
const subAffix = listSelectedSubStats.find((item) => item.property === value.property);
|
||||
if (subAffix && value.property !== mainAffixMap[selectedMainStat]?.property) {
|
||||
const subAffix = listSelectedSubStats.find((item) => item.property === value.Property);
|
||||
if (subAffix && value.Property !== mainAffixMap[selectedMainStat]?.Property) {
|
||||
listSet[key] = value;
|
||||
}
|
||||
}
|
||||
return listSet;
|
||||
}, [mapSubAffix, listSelectedSubStats, mapMainAffix, selectedRelicSlot, selectedMainStat]);
|
||||
}, [subAffix, listSelectedSubStats, mainAffix, selectedRelicSlot, selectedMainStat]);
|
||||
|
||||
const effectBonus = useMemo(() => {
|
||||
const affixSet = mapMainAffix?.["5" + selectedRelicSlot];
|
||||
const affixSet = mainAffix?.["5" + selectedRelicSlot];
|
||||
if (!affixSet) return 0;
|
||||
|
||||
const data = affixSet[selectedMainStat];
|
||||
if (!data) return 0;
|
||||
|
||||
return calcMainAffixBonus(data, selectedRelicLevel);
|
||||
}, [mapMainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
|
||||
}, [mainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
|
||||
|
||||
const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => {
|
||||
setError("");
|
||||
@@ -136,7 +137,7 @@ export default function RelicMaker() {
|
||||
return;
|
||||
}
|
||||
newSubAffixes[index].affixId = key;
|
||||
newSubAffixes[index].property = subAffixOptions[key].property;
|
||||
newSubAffixes[index].property = subAffixOptions[key].Property;
|
||||
newSubAffixes[index].rollCount = rollCount;
|
||||
newSubAffixes[index].stepCount = stepCount;
|
||||
setListSelectedSubStats(newSubAffixes);
|
||||
@@ -179,7 +180,7 @@ export default function RelicMaker() {
|
||||
exKeys.push(randomKey);
|
||||
const randomValue = subAffixOptions[randomKey];
|
||||
newSubAffixes[i].affixId = randomKey;
|
||||
newSubAffixes[i].property = randomValue.property;
|
||||
newSubAffixes[i].property = randomValue.Property;
|
||||
newSubAffixes[i].rollCount = 0;
|
||||
newSubAffixes[i].stepCount = 0;
|
||||
}
|
||||
@@ -205,7 +206,7 @@ export default function RelicMaker() {
|
||||
|
||||
const handlerSaveRelic = () => {
|
||||
setError("");
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const avatar = avatars[avatarSelected?.ID?.toString() || ""];
|
||||
if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) {
|
||||
setError(transI18n("pleaseSelectAllOptions"));
|
||||
return;
|
||||
@@ -258,10 +259,10 @@ export default function RelicMaker() {
|
||||
<div>
|
||||
<label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label>
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(mapMainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
|
||||
customSet={Object.entries(mainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
|
||||
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={selectedMainStat}
|
||||
@@ -275,8 +276,8 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(relicSets).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: value.Name,
|
||||
imageUrl: `${process.env.CDN_URL}/spriteoutput/itemfigures/${value.Icon.match(/\d+/)?.[0]}.png`
|
||||
label: getLocaleName(locale, value.Name),
|
||||
imageUrl: `${process.env.CDN_URL}/${value.Image.SetIconPath}`
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={selectedRelicSet}
|
||||
@@ -288,15 +289,15 @@ export default function RelicMaker() {
|
||||
|
||||
{/* Set Bonus Display */}
|
||||
<div className="mb-6 py-4 bg-base-100 rounded-lg">
|
||||
{selectedRelicSet !== "" ? Object.entries(mapRelicInfo[selectedRelicSet].RequireNum).map(([key, value]) => (
|
||||
{selectedRelicSet !== "" ? Object.entries(mapRelicSet[selectedRelicSet].Skills).map(([key, value]) => (
|
||||
<div key={key} className="text-blue-300 text-sm mb-1">
|
||||
<span className="text-info font-bold">{key}-Pc:
|
||||
<div
|
||||
className="text-warning leading-relaxed font-bold"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
value.Desc,
|
||||
value.ParamList || []
|
||||
getLocaleName(locale, value.Desc),
|
||||
value.Param || []
|
||||
)
|
||||
}}
|
||||
/> </span>
|
||||
@@ -384,13 +385,13 @@ export default function RelicMaker() {
|
||||
<SelectCustomImage
|
||||
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
|
||||
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
|
||||
}))}
|
||||
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
|
||||
value: key,
|
||||
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
|
||||
imageUrl: mappingStats[value.property].icon
|
||||
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
|
||||
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
|
||||
}))}
|
||||
selectedCustomSet={v.affixId}
|
||||
placeholder={transI18n("selectASubStat")}
|
||||
@@ -401,7 +402,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)}{mappingStats?.[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>
|
||||
|
||||
{/* Up Roll Values */}
|
||||
@@ -415,19 +416,19 @@ export default function RelicMaker() {
|
||||
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
|
||||
className="btn btn-sm btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[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 btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[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 btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -443,19 +444,19 @@ export default function RelicMaker() {
|
||||
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))}
|
||||
className="btn btn-sm btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))}
|
||||
className="btn btn-sm btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))}
|
||||
className="btn btn-sm btn-info border-0"
|
||||
>
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
|
||||
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,18 +6,18 @@ import { motion } from "framer-motion";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
import RelicCard from "../card/relicCard";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import useRelicStore from "@/stores/relicStore";
|
||||
import useModelStore from '@/stores/modelStore';
|
||||
import { replaceByParam } from '@/helper';
|
||||
import useRelicMakerStore from '@/stores/relicMakerStore';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import QuickView from "../quickView";
|
||||
import { ModalConfig } from "@/types";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { getLocaleName } from '@/helper';
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
|
||||
export default function RelicsInfo() {
|
||||
const { avatars, setAvatars } = useUserDataStore()
|
||||
const { avatarSelected } = useAvatarStore()
|
||||
const {
|
||||
setSelectedRelicSlot,
|
||||
selectedRelicSlot,
|
||||
@@ -29,7 +29,7 @@ export default function RelicsInfo() {
|
||||
resetSubStat,
|
||||
listSelectedSubStats,
|
||||
} = useRelicMakerStore()
|
||||
const { mapSubAffix } = useAffixStore()
|
||||
|
||||
const {
|
||||
isOpenRelic,
|
||||
setIsOpenRelic,
|
||||
@@ -37,9 +37,10 @@ export default function RelicsInfo() {
|
||||
setIsOpenQuickView
|
||||
} = useModelStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
const { mapRelicInfo } = useRelicStore()
|
||||
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { mapRelicSet, subAffix } = useDetailDataStore()
|
||||
const { locale } = useLocaleStore()
|
||||
|
||||
const handleShow = (modalId: string) => {
|
||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||
if (modal) {
|
||||
@@ -56,8 +57,8 @@ export default function RelicsInfo() {
|
||||
};
|
||||
|
||||
|
||||
const getRelic = useCallback((slot: string) => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const getRelic = useCallback((slot: string) => {
|
||||
const avatar = avatars[avatarSelected?.ID.toString() || ""];
|
||||
if (avatar) {
|
||||
return avatar.profileList[avatar.profileSelect]?.relics[slot] || null;
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export default function RelicsInfo() {
|
||||
}, [avatars, avatarSelected]);
|
||||
|
||||
const handlerDeleteRelic = (slot: string) => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const avatar = avatars[avatarSelected?.ID.toString() || ""];
|
||||
if (avatar) {
|
||||
delete avatar.profileList[avatar.profileSelect].relics[slot]
|
||||
setAvatars({ ...avatars });
|
||||
@@ -84,7 +85,7 @@ export default function RelicsInfo() {
|
||||
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
|
||||
relic.sub_affixes.forEach((item, index) => {
|
||||
newSubAffixes[index].affixId = item.sub_affix_id.toString();
|
||||
newSubAffixes[index].property = mapSubAffix["5"][item.sub_affix_id.toString()]?.property || "";
|
||||
newSubAffixes[index].property = subAffix["5"][item.sub_affix_id.toString()]?.Property || "";
|
||||
newSubAffixes[index].rollCount = item.count || 0;
|
||||
newSubAffixes[index].stepCount = item.step || 0;
|
||||
})
|
||||
@@ -108,7 +109,7 @@ export default function RelicsInfo() {
|
||||
}
|
||||
|
||||
const relicEffects = useMemo(() => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const avatar = avatars[avatarSelected?.ID.toString() || ""];
|
||||
const relicCount: { [key: string]: number } = {};
|
||||
if (avatar) {
|
||||
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
|
||||
@@ -199,7 +200,7 @@ export default function RelicsInfo() {
|
||||
>
|
||||
<RelicCard
|
||||
slot={item}
|
||||
avatarId={avatarSelected?.id || ""}
|
||||
avatarId={avatarSelected?.ID.toString() || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -284,7 +285,7 @@ export default function RelicsInfo() {
|
||||
|
||||
<div className="space-y-6">
|
||||
{relicEffects.map((setEffect, index) => {
|
||||
const relicInfo = mapRelicInfo[setEffect.key];
|
||||
const relicInfo = mapRelicSet[setEffect.key];
|
||||
if (!relicInfo) return null;
|
||||
return (
|
||||
<div key={index} className="space-y-3">
|
||||
@@ -293,7 +294,7 @@ export default function RelicsInfo() {
|
||||
className="font-bold text-warning"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
relicInfo.Name,
|
||||
getLocaleName(locale, relicInfo.Name),
|
||||
[]
|
||||
)
|
||||
}}
|
||||
@@ -306,7 +307,7 @@ export default function RelicsInfo() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pl-4">
|
||||
{Object.entries(relicInfo.RequireNum).map(([requireNum, value]) => {
|
||||
{Object.entries(relicInfo.Skills).map(([requireNum, value]) => {
|
||||
if (Number(requireNum) > Number(setEffect.count)) return null;
|
||||
return (
|
||||
<div key={requireNum} className="space-y-1">
|
||||
@@ -317,8 +318,8 @@ export default function RelicsInfo() {
|
||||
className="text-sm text-base-content/80 leading-relaxed pl-4"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
value.Desc,
|
||||
value.ParamList || []
|
||||
getLocaleName(locale, value.Desc),
|
||||
value.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { FastAverageColor } from 'fast-average-color';
|
||||
import NextImage from 'next/image';
|
||||
import ParseText from '../parseText';
|
||||
@@ -8,27 +7,24 @@ import useLocaleStore from '@/stores/localeStore';
|
||||
import { calcAffixBonus, calcBaseStat, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, convertToRoman, getNameChar, replaceByParam } from '@/helper';
|
||||
import useUserDataStore from '@/stores/userDataStore';
|
||||
import { traceShowCaseMap } from '@/constant/traceConstant';
|
||||
import { StatusAddType } from '@/types';
|
||||
import { mappingStats } from '@/constant/constant';
|
||||
import useLightconeStore from '@/stores/lightconeStore';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import useRelicStore from '@/stores/relicStore';
|
||||
import { toast } from 'react-toastify';
|
||||
import RelicShowcase from './relicShowcase';
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
import useCurrentDataStore from '@/stores/currentDataStore';
|
||||
import { getLocaleName } from '@/helper';
|
||||
|
||||
|
||||
export default function ShowCaseInfo() {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
|
||||
const { mapLightconeInfo } = useLightconeStore()
|
||||
const { mapMainAffix, mapSubAffix } = useAffixStore()
|
||||
const { avatarSelected } = useCurrentDataStore()
|
||||
const { mainAffix, subAffix, mapRelicSet, mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore()
|
||||
const { avatars } = useUserDataStore()
|
||||
const [avgColor, setAvgColor] = useState('#222');
|
||||
const imgRef = useRef(null);
|
||||
const cardRef = useRef(null)
|
||||
const { locale } = useLocaleStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { mapRelicInfo } = useRelicStore()
|
||||
|
||||
const handleSaveImage = useCallback(() => {
|
||||
if (cardRef.current === null || !avatarSelected) {
|
||||
@@ -52,25 +48,24 @@ export default function ShowCaseInfo() {
|
||||
link.click();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
toast.error("Error generating showcase card!");
|
||||
});
|
||||
}, [avatarSelected, locale, transI18n]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!avatarSelected?.id) return;
|
||||
if (!avatarSelected) return;
|
||||
const fac = new FastAverageColor();
|
||||
const img = new Image();
|
||||
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.src = `${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`;
|
||||
img.src = `${process.env.CDN_URL}/${avatarSelected?.Image?.AvatarCutinFrontImgPath}`;
|
||||
|
||||
img.onload = () => {
|
||||
fac.getColorAsync(img)
|
||||
.then((color) => {
|
||||
setAvgColor(color.hex);
|
||||
})
|
||||
.catch(e => console.error("Vẫn lỗi CORS:", e));
|
||||
.catch(e => console.error("Error:", e));
|
||||
};
|
||||
return () => {
|
||||
fac.destroy();
|
||||
@@ -78,22 +73,17 @@ export default function ShowCaseInfo() {
|
||||
};
|
||||
}, [avatarSelected]);
|
||||
|
||||
const avatarInfo = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return mapAvatarInfo[avatarSelected.id]
|
||||
}, [avatarSelected, mapAvatarInfo])
|
||||
|
||||
const avatarSkillTree = useMemo(() => {
|
||||
if (!avatarSelected || !avatars[avatarSelected.id]) return {}
|
||||
if (avatars[avatarSelected.id].enhanced) {
|
||||
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {}
|
||||
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
|
||||
if (avatars[avatarSelected?.ID?.toString()].enhanced) {
|
||||
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
|
||||
}
|
||||
return avatarInfo?.SkillTrees || {}
|
||||
}, [avatarSelected, avatarInfo, avatars])
|
||||
return avatarSelected?.SkillTrees || {}
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarData = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return avatars[avatarSelected.id]
|
||||
return avatars[avatarSelected?.ID?.toString()]
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const avatarProfile = useMemo(() => {
|
||||
@@ -102,23 +92,23 @@ export default function ShowCaseInfo() {
|
||||
}, [avatarSelected, avatarData])
|
||||
|
||||
const lightconeStats = useMemo(() => {
|
||||
if (!avatarSelected || !avatarProfile?.lightcone || !mapLightconeInfo[avatarProfile?.lightcone?.item_id]) return
|
||||
if (!avatarSelected || !avatarProfile?.lightcone || !mapLightCone[avatarProfile?.lightcone?.item_id]) return
|
||||
const promotion = calcPromotion(avatarProfile?.lightcone?.level)
|
||||
const atkStat = calcBaseStat(
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttack,
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttackAdd,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttack,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttackAdd,
|
||||
0,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
const hpStat = calcBaseStat(
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHP,
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHPAdd,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHP,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHPAdd,
|
||||
0,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
const defStat = calcBaseStat(
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefence,
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefenceAdd,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefence,
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefenceAdd,
|
||||
0,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
@@ -127,10 +117,10 @@ export default function ShowCaseInfo() {
|
||||
hp: hpStat,
|
||||
def: defStat,
|
||||
}
|
||||
}, [avatarSelected, mapLightconeInfo, avatarProfile])
|
||||
}, [avatarSelected, mapLightCone, avatarProfile])
|
||||
|
||||
const relicEffects = useMemo(() => {
|
||||
const avatar = avatars[avatarSelected?.id || ""];
|
||||
const avatar = avatars[avatarSelected?.ID?.toString() || ""];
|
||||
const relicCount: { [key: string]: number } = {};
|
||||
if (avatar) {
|
||||
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
|
||||
@@ -151,45 +141,45 @@ export default function ShowCaseInfo() {
|
||||
}, [avatars, avatarSelected]);
|
||||
|
||||
const relicStats = useMemo(() => {
|
||||
if (!avatarSelected || !avatarProfile?.relics || !mapMainAffix || !mapSubAffix) return
|
||||
if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return
|
||||
|
||||
return Object.entries(avatarProfile?.relics).map(([key, value]) => {
|
||||
const mainAffixMap = mapMainAffix["5" + key]
|
||||
const subAffixMap = mapSubAffix["5"]
|
||||
const mainAffixMap = mainAffix["5" + key]
|
||||
const subAffixMap = subAffix["5"]
|
||||
if (!mainAffixMap || !subAffixMap) return
|
||||
return {
|
||||
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
|
||||
mainAffix: {
|
||||
property: mainAffixMap?.[value?.main_affix_id]?.property,
|
||||
property: mainAffixMap?.[value?.main_affix_id]?.Property,
|
||||
level: value?.level,
|
||||
valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level),
|
||||
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.property]
|
||||
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property]
|
||||
},
|
||||
subAffix: value?.sub_affixes?.map((subValue) => {
|
||||
return {
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.property,
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.Property,
|
||||
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property],
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property],
|
||||
step: subValue?.step,
|
||||
count: subValue?.count
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [avatarSelected, avatarProfile, mapMainAffix, mapSubAffix])
|
||||
}, [avatarSelected, avatarProfile, mainAffix, subAffix])
|
||||
|
||||
const totalSubStats = useMemo(() => {
|
||||
if (!relicStats?.length) return 0
|
||||
return (relicStats ?? []).reduce((acc, relic) => {
|
||||
const subAffixList = relic?.subAffix ?? []
|
||||
return acc + subAffixList.reduce((subAcc, subAffix) => {
|
||||
if (avatarInfo?.Relics?.SubAffixPropertyList.findIndex(it => it === subAffix.property) !== -1) {
|
||||
if (avatarSelected?.Relics?.SubAffixPropertyList.findIndex(it => it === subAffix.property) !== -1) {
|
||||
return subAcc + (subAffix?.count ?? 0)
|
||||
}
|
||||
return subAcc
|
||||
}, 0)
|
||||
}, 0)
|
||||
}, [relicStats, avatarInfo])
|
||||
}, [relicStats, avatarSelected])
|
||||
|
||||
const characterStats = useMemo(() => {
|
||||
if (!avatarSelected || !avatarData) return
|
||||
@@ -205,73 +195,73 @@ export default function ShowCaseInfo() {
|
||||
}> = {
|
||||
HP: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "HP",
|
||||
icon: "/icon/hp.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
ATK: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "ATK",
|
||||
icon: "/icon/attack.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconAttack.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
DEF: {
|
||||
value: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
|
||||
avatarData.level
|
||||
),
|
||||
base: calcBaseStatRaw(
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
|
||||
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
|
||||
avatarData.level
|
||||
),
|
||||
name: "DEF",
|
||||
icon: "/icon/defence.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconDefence.png",
|
||||
unit: "",
|
||||
round: 0
|
||||
},
|
||||
SPD: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
|
||||
name: "SPD",
|
||||
icon: "/icon/speed.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconSpeed.png",
|
||||
unit: "",
|
||||
round: 1
|
||||
},
|
||||
CRITRate: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
|
||||
name: "CRIT Rate",
|
||||
icon: "/icon/crit-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
CRITDmg: {
|
||||
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
|
||||
name: "CRIT DMG",
|
||||
icon: "/icon/crit-damage.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -279,7 +269,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Break Effect",
|
||||
icon: "/icon/break-effect.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -287,7 +277,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Effect RES",
|
||||
icon: "/icon/effect-res.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -295,7 +285,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Energy Rate",
|
||||
icon: "/icon/energy-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -303,7 +293,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Effect Hit Rate",
|
||||
icon: "/icon/effect-hit-rate.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -311,7 +301,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Healing Boost",
|
||||
icon: "/icon/healing-boost.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -319,7 +309,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Physical Boost",
|
||||
icon: "/icon/physical-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -327,7 +317,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Fire Boost",
|
||||
icon: "/icon/fire-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -335,7 +325,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Ice Boost",
|
||||
icon: "/icon/ice-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -343,7 +333,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Thunder Boost",
|
||||
icon: "/icon/thunder-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -351,7 +341,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Wind Boost",
|
||||
icon: "/icon/wind-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -359,7 +349,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Quantum Boost",
|
||||
icon: "/icon/quantum-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -367,7 +357,7 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Imaginary Boost",
|
||||
icon: "/icon/imaginary-add.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
@@ -375,57 +365,57 @@ export default function ShowCaseInfo() {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Elation Boost",
|
||||
icon: "/icon/IconJoy.webp",
|
||||
icon: "spriteoutput/ui/avatar/icon/IconJoy.png",
|
||||
unit: "%",
|
||||
round: 1
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) {
|
||||
if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
|
||||
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
|
||||
statsData.HP.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.HP.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.ATK.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.ATK.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.DEF.value += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
statsData.DEF.base += calcBaseStatRaw(
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
|
||||
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
|
||||
avatarProfile?.lightcone?.level
|
||||
)
|
||||
|
||||
const bonusData = mapLightconeInfo[avatarProfile?.lightcone?.item_id].Bonus?.[avatarProfile?.lightcone.rank - 1]
|
||||
const bonusData = mapLightCone[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus
|
||||
if (bonusData && bonusData.length > 0) {
|
||||
const bonusSpd = bonusData.filter((bonus) => bonus.type === "BaseSpeed")
|
||||
const bonusOther = bonusData.filter((bonus) => bonus.type !== "BaseSpeed")
|
||||
const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed")
|
||||
const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed")
|
||||
bonusSpd.forEach((bonus) => {
|
||||
statsData.SPD.value += bonus.value
|
||||
statsData.SPD.base += bonus.value
|
||||
statsData.SPD.value += bonus.Value
|
||||
statsData.SPD.base += bonus.Value
|
||||
})
|
||||
bonusOther.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.type]?.baseStat
|
||||
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
|
||||
if (statsBase && statsData[statsBase]) {
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value)
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -448,19 +438,17 @@ export default function ShowCaseInfo() {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (avatarProfile?.relics && mapMainAffix && mapSubAffix) {
|
||||
if (avatarProfile?.relics && mainAffix && subAffix) {
|
||||
Object.entries(avatarProfile?.relics).forEach(([key, value]) => {
|
||||
const mainAffixMap = mapMainAffix["5" + key]
|
||||
const subAffixMap = mapSubAffix["5"]
|
||||
const mainAffixMap = mainAffix["5" + key]
|
||||
const subAffixMap = subAffix["5"]
|
||||
if (!mainAffixMap || !subAffixMap) return
|
||||
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.property]?.baseStat
|
||||
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat
|
||||
if (mainStats && statsData[mainStats]) {
|
||||
statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base)
|
||||
}
|
||||
value?.sub_affixes.forEach((subValue) => {
|
||||
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.property]?.baseStat
|
||||
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat
|
||||
if (subStats && statsData[subStats]) {
|
||||
statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base)
|
||||
}
|
||||
@@ -470,14 +458,14 @@ export default function ShowCaseInfo() {
|
||||
|
||||
if (relicEffects && relicEffects.length > 0) {
|
||||
relicEffects.forEach((relic) => {
|
||||
const dataBonus = mapRelicInfo?.[relic.key]?.Bonus
|
||||
const dataBonus = mapRelicSet?.[relic.key]?.Skills
|
||||
if (!dataBonus || Object.keys(dataBonus).length === 0) return
|
||||
Object.entries(dataBonus || {}).forEach(([key, value]) => {
|
||||
if (relic.count < Number(key)) return
|
||||
value.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.type]?.baseStat
|
||||
value.Bonus.forEach((bonus) => {
|
||||
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
|
||||
if (statsBase && statsData[statsBase]) {
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value)
|
||||
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -489,14 +477,14 @@ export default function ShowCaseInfo() {
|
||||
}, [
|
||||
avatarSelected,
|
||||
avatarData,
|
||||
mapAvatarInfo,
|
||||
mapAvatar,
|
||||
avatarProfile?.lightcone,
|
||||
avatarProfile?.relics,
|
||||
mapLightconeInfo,
|
||||
mapMainAffix,
|
||||
mapSubAffix,
|
||||
mapLightCone,
|
||||
mainAffix,
|
||||
subAffix,
|
||||
relicEffects,
|
||||
mapRelicInfo,
|
||||
mapRelicSet,
|
||||
avatarSkillTree
|
||||
])
|
||||
|
||||
@@ -508,19 +496,6 @@ export default function ShowCaseInfo() {
|
||||
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
||||
}, [])
|
||||
|
||||
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
|
||||
if (!icon) return
|
||||
if (icon.startsWith("SkillIcon")) {
|
||||
return `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/${icon}`
|
||||
} else if (status && mappingStats[status.PropertyType]) {
|
||||
return mappingStats[status.PropertyType].icon
|
||||
}
|
||||
else if (icon.startsWith("Icon")) {
|
||||
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
|
||||
}
|
||||
return ""
|
||||
}, [avatarSelected?.id])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start m-1 text-white">
|
||||
<div className="flex items-center justify-start mt-4 mb-4">
|
||||
@@ -550,7 +525,7 @@ export default function ShowCaseInfo() {
|
||||
ref={imgRef}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`}
|
||||
src={`${process.env.CDN_URL}/${avatarSelected?.Image?.AvatarCutinFrontImgPath}`}
|
||||
className="object-contain scale-[2] overflow-hidden"
|
||||
alt="Character Preview"
|
||||
width={1024}
|
||||
@@ -576,9 +551,9 @@ export default function ShowCaseInfo() {
|
||||
>
|
||||
|
||||
<div className="absolute top-4 left-3">
|
||||
{avatarSelected && avatarInfo && avatarData?.data && typeof avatarData?.data?.rank === "number" && (
|
||||
{avatarSelected && avatarSelected && avatarData?.data && typeof avatarData?.data?.rank === "number" && (
|
||||
<div className="flex flex-col items-center gap-2 py-2">
|
||||
{avatarInfo?.RankIcon?.map((src, index) => {
|
||||
{Object.values(avatarSelected?.Ranks || {})?.map((rank, index) => {
|
||||
const isActive = avatarData?.data?.rank > index;
|
||||
return (
|
||||
<div
|
||||
@@ -621,7 +596,7 @@ export default function ShowCaseInfo() {
|
||||
}}
|
||||
>
|
||||
<NextImage
|
||||
src={src ?? null}
|
||||
src={`${process.env.CDN_URL}/${rank.Icon}`}
|
||||
alt="Rank Icon"
|
||||
width={125}
|
||||
height={125}
|
||||
@@ -664,20 +639,20 @@ export default function ShowCaseInfo() {
|
||||
<NextImage
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${baseType[avatarSelected?.BaseType]?.Icon}`}
|
||||
alt="Path Icon"
|
||||
width={32}
|
||||
height={32}
|
||||
className="h-auto w-8"
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<NextImage
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${avatarSelected?.damageType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${damageType[avatarSelected?.DamageType]?.Icon}`}
|
||||
alt="Element Icon"
|
||||
width={32}
|
||||
height={32}
|
||||
className="h-auto w-8" />
|
||||
className="h-8 w-8" />
|
||||
|
||||
</div>
|
||||
)}
|
||||
@@ -691,7 +666,7 @@ export default function ShowCaseInfo() {
|
||||
<NextImage
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`}
|
||||
src={`${process.env.CDN_URL}/${baseType[avatarSelected?.BaseType]?.Icon}`}
|
||||
alt="Path Icon"
|
||||
width={160}
|
||||
height={160}
|
||||
@@ -701,8 +676,8 @@ export default function ShowCaseInfo() {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{avatarData && avatarInfo && avatarSkillTree && traceShowCaseMap[avatarSelected?.baseType || ""]
|
||||
&& Object.values(traceShowCaseMap[avatarSelected?.baseType || ""] || []).map((item, index) => {
|
||||
{avatarData && avatarSelected && avatarSkillTree && traceShowCaseMap[avatarSelected?.BaseType || ""]
|
||||
&& Object.values(traceShowCaseMap[avatarSelected?.BaseType || ""] || []).map((item, index) => {
|
||||
|
||||
return (
|
||||
<div key={`row-${index}`} className="flex flex-row items-center">
|
||||
@@ -742,26 +717,15 @@ export default function ShowCaseInfo() {
|
||||
${avatarData.data.skills[avatarSkillTree?.[btn.id]?.["1"]?.PointID] ? "" : "opacity-50"}
|
||||
`}
|
||||
>
|
||||
{
|
||||
(() => {
|
||||
const skillImg = getImageSkill(
|
||||
avatarInfo.SkillTrees?.[btn.id]?.["1"]?.Icon,
|
||||
avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]
|
||||
);
|
||||
|
||||
return skillImg ? (
|
||||
<NextImage
|
||||
src={skillImg}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt={btn.id}
|
||||
width={125}
|
||||
height={125}
|
||||
className={`h-full ${imageSize} ${filterClass}`}
|
||||
/>
|
||||
) : null;
|
||||
})()
|
||||
}
|
||||
<NextImage
|
||||
src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt={btn.id}
|
||||
width={125}
|
||||
height={125}
|
||||
className={`h-full ${imageSize} ${filterClass}`}
|
||||
/>
|
||||
|
||||
{(isBig || isBigMemory) && (
|
||||
<span className="absolute bottom-0 left-0 text-[12px] text-white bg-black/70 px-1 rounded-sm">
|
||||
@@ -872,8 +836,8 @@ export default function ShowCaseInfo() {
|
||||
>
|
||||
{[...Array(
|
||||
Number(
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id]?.Rarity?.[
|
||||
mapLightconeInfo[avatarProfile?.lightcone?.item_id]?.Rarity.length - 1
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id]?.Rarity?.[
|
||||
mapLightCone[avatarProfile?.lightcone?.item_id]?.Rarity.length - 1
|
||||
] || 0
|
||||
)
|
||||
)].map((_, i) => (
|
||||
@@ -890,7 +854,7 @@ export default function ShowCaseInfo() {
|
||||
<ParseText
|
||||
className="text-lg font-semibold"
|
||||
locale={locale}
|
||||
text={mapLightconeInfo[avatarProfile?.lightcone?.item_id].Name}
|
||||
text={getLocaleName(locale, mapLightCone[avatarProfile?.lightcone?.item_id].Name)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -910,7 +874,7 @@ export default function ShowCaseInfo() {
|
||||
<NextImage
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src="/icon/hp.webp"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconMaxHP.png`}
|
||||
alt="HP"
|
||||
width={16}
|
||||
height={16}
|
||||
@@ -922,7 +886,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
|
||||
<NextImage
|
||||
src="/icon/attack.webp"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconAttack.png`}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt="ATK"
|
||||
@@ -938,7 +902,7 @@ export default function ShowCaseInfo() {
|
||||
<NextImage
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src="/icon/defence.webp"
|
||||
src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconDefence.png`}
|
||||
alt="DEF"
|
||||
width={16}
|
||||
height={16}
|
||||
@@ -964,13 +928,13 @@ export default function ShowCaseInfo() {
|
||||
return (
|
||||
<div key={index} className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<NextImage src={stat?.icon || ""}
|
||||
<NextImage src={`${process.env.CDN_URL}/${stat?.icon}`}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
alt="Stat Icon"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-auto w-10 p-2"
|
||||
className="h-10 w-10 p-2"
|
||||
/>
|
||||
<span className="font-bold">{stat.name}</span>
|
||||
</div>
|
||||
@@ -986,7 +950,7 @@ export default function ShowCaseInfo() {
|
||||
|
||||
<div className="flex flex-col items-center gap-1 w-full my-2">
|
||||
{relicEffects.map((setEffect, index) => {
|
||||
const relicInfo = mapRelicInfo[setEffect.key];
|
||||
const relicInfo = mapRelicSet[setEffect.key];
|
||||
if (!relicInfo) return null;
|
||||
return (
|
||||
<div key={index} className="flex w-full flex-row justify-between text-left">
|
||||
@@ -997,7 +961,7 @@ export default function ShowCaseInfo() {
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
relicInfo.Name,
|
||||
getLocaleName(locale, relicInfo.Name),
|
||||
[]
|
||||
)
|
||||
}}
|
||||
@@ -1015,9 +979,9 @@ export default function ShowCaseInfo() {
|
||||
<div className="flex h-162.5 flex-col justify-between py-3 mr-1 text-lg w-full" >
|
||||
|
||||
{relicStats?.map((relic, index) => {
|
||||
if (!relic || !avatarInfo) return null
|
||||
if (!relic || !avatarSelected) return null
|
||||
return (
|
||||
<RelicShowcase key={index} relic={relic} avatarInfo={avatarInfo} />
|
||||
<RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} />
|
||||
)
|
||||
})}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"use client"
|
||||
|
||||
import NextImage from "next/image"
|
||||
import { CharacterDetail, RelicShowcaseType } from "@/types";
|
||||
import { AvatarDetail, RelicShowcaseType } from "@/types";
|
||||
|
||||
export default function RelicShowcase({
|
||||
relic,
|
||||
avatarInfo,
|
||||
}: {
|
||||
relic: RelicShowcaseType;
|
||||
avatarInfo: CharacterDetail;
|
||||
avatarInfo: AvatarDetail;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -28,7 +28,7 @@ export default function RelicShowcase({
|
||||
width={78}
|
||||
height={78}
|
||||
alt="Relic Icon"
|
||||
className="h-auto w-19.5 rounded-lg"
|
||||
className="h-19.5 w-19.5 rounded-lg"
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -48,13 +48,13 @@ export default function RelicShowcase({
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
|
||||
<NextImage
|
||||
src={relic?.mainAffix?.detail?.icon || ""}
|
||||
src={`${process.env.CDN_URL}/${relic?.mainAffix?.detail?.icon}` || ""}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
width={35}
|
||||
height={35}
|
||||
alt="Main Affix Icon"
|
||||
className="h-auto w-8.75"
|
||||
className="h-8.75 w-8.75"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
|
||||
@@ -75,13 +75,13 @@ export default function RelicShowcase({
|
||||
<div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0">
|
||||
{subAffix?.detail?.icon ? (
|
||||
<NextImage
|
||||
src={subAffix?.detail?.icon || ""}
|
||||
src={`${process.env.CDN_URL}/${subAffix?.detail?.icon}` || ""}
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
width={32}
|
||||
height={32}
|
||||
alt="Sub Affix Icon"
|
||||
className="h-auto w-6 shrink-0"
|
||||
className="h-6 w-6 shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0">
|
||||
|
||||
@@ -1,64 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { 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 { replaceByParam, getLocaleName } from '@/helper';
|
||||
import { mappingStats } from "@/constant/constant";
|
||||
import { StatusAddType } from "@/types";
|
||||
import { toast } from "react-toastify";
|
||||
import useCurrentDataStore from "@/stores/currentDataStore";
|
||||
import { StatusAdd } from '@/types/avatarDetail';
|
||||
|
||||
export default function SkillsInfo() {
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { theme } = useLocaleStore()
|
||||
const { avatarSelected, mapAvatarInfo, skillSelected, setSkillSelected } = useAvatarStore()
|
||||
const { avatarSelected, skillIDSelected, setSkillIDSelected } = useCurrentDataStore()
|
||||
const { avatars, setAvatar } = useUserDataStore()
|
||||
|
||||
const { locale } = useLocaleStore()
|
||||
const traceButtons = useMemo(() => {
|
||||
if (!avatarSelected) return
|
||||
return traceButtonsInfo[avatarSelected.baseType]
|
||||
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]
|
||||
return avatars[avatarSelected?.ID?.toString()]
|
||||
}, [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 || {}
|
||||
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
|
||||
if (avatars[avatarSelected?.ID?.toString()].enhanced) {
|
||||
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
|
||||
}
|
||||
return avatarInfo?.SkillTrees || {}
|
||||
}, [avatarSelected, avatarInfo, avatars])
|
||||
return avatarSelected?.SkillTrees || {}
|
||||
}, [avatarSelected, avatars])
|
||||
|
||||
const skillInfo = useMemo(() => {
|
||||
if (!avatarSelected || !skillSelected) return
|
||||
return avatarSkillTree?.[skillSelected || ""]?.["1"]
|
||||
}, [avatarSelected, avatarSkillTree, skillSelected])
|
||||
if (!avatarSelected || !skillIDSelected) return
|
||||
return avatarSkillTree?.[skillIDSelected || ""]?.["1"]
|
||||
}, [avatarSelected, avatarSkillTree, skillIDSelected])
|
||||
|
||||
const getImageSkill = (icon: string | undefined, status: StatusAddType | undefined) => {
|
||||
if (!icon) return
|
||||
const urlPrefix = `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/`;
|
||||
if (icon.startsWith("SkillIcon")) {
|
||||
return `${urlPrefix}${icon}`
|
||||
} else if (status && mappingStats[status.PropertyType]) {
|
||||
return mappingStats[status.PropertyType].icon
|
||||
} else if (icon.startsWith("Icon")) {
|
||||
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
|
||||
}
|
||||
}
|
||||
|
||||
const getTraceBuffDisplay = (status: StatusAddType) => {
|
||||
const getTraceBuffDisplay = (status: StatusAdd) => {
|
||||
const dataDisplay = mappingStats[status.PropertyType]
|
||||
if (!dataDisplay) return ""
|
||||
if (dataDisplay.unit === "%") {
|
||||
@@ -72,11 +55,12 @@ export default function SkillsInfo() {
|
||||
|
||||
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 (!avatarSelected || !avatarData) return undefined
|
||||
let result = Object.values(avatarSelected.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))
|
||||
result = Object.values(avatarSelected?.Enhanced?.[avatarData.enhanced.toString()]?.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
|
||||
}
|
||||
|
||||
if (result && result.length > 0) {
|
||||
return {
|
||||
isServant: false,
|
||||
@@ -84,25 +68,22 @@ export default function SkillsInfo() {
|
||||
servantData: null,
|
||||
}
|
||||
}
|
||||
const resultServant = Object.entries(avatarInfo.Memosprite?.Skills || {})
|
||||
?.filter(([skillId]) => skillIds.includes(Number(skillId)))
|
||||
?.map(([skillId, skillData]) => ({
|
||||
Id: Number(skillId),
|
||||
...skillData,
|
||||
}))
|
||||
const resultServant = Object.values(avatarSelected?.Memosprite?.Skills || {})
|
||||
?.filter((skill) => skillIds.includes(skill.ID))
|
||||
|
||||
if (resultServant && resultServant.length > 0) {
|
||||
return {
|
||||
isServant: true,
|
||||
data: resultServant,
|
||||
servantData: avatarInfo.Memosprite,
|
||||
servantData: avatarSelected.Memosprite,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarInfo, avatarData])
|
||||
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarData])
|
||||
|
||||
|
||||
const handlerMaxAll = () => {
|
||||
if (!avatarInfo || !avatarData || !avatarSkillTree) {
|
||||
if (!avatarData || !avatarSkillTree) {
|
||||
toast.error(transI18n("maxAllFailed"))
|
||||
return
|
||||
}
|
||||
@@ -123,8 +104,8 @@ export default function SkillsInfo() {
|
||||
const newData = structuredClone(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
|
||||
|
||||
if (!status && traceLink?.[avatarSelected?.baseType || ""]?.[skillSelected || ""]) {
|
||||
traceLink[avatarSelected?.baseType || ""][skillSelected || ""].forEach((pointId) => {
|
||||
if (!status && traceLink?.[avatarSelected?.BaseType || ""]?.[skillIDSelected || ""]) {
|
||||
traceLink[avatarSelected?.BaseType || ""][skillIDSelected || ""].forEach((pointId) => {
|
||||
if (avatarSkillTree?.[pointId]?.["1"]) {
|
||||
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
|
||||
}
|
||||
@@ -143,12 +124,12 @@ export default function SkillsInfo() {
|
||||
</h2>
|
||||
<div className="flex flex-col items-center">
|
||||
<button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button>
|
||||
{traceButtons && avatarInfo && (
|
||||
{traceButtons && avatarSelected && (
|
||||
<div className="grid col-span-4 relative w-full aspect-square">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`}
|
||||
src={`/skilltree/${avatarSelected?.BaseType?.toUpperCase()}.webp`}
|
||||
alt=""
|
||||
width={312}
|
||||
priority={true}
|
||||
@@ -159,7 +140,7 @@ export default function SkillsInfo() {
|
||||
className={`w-full h-full object-cover rounded-xl`}
|
||||
/>
|
||||
{traceButtons.map((btn, index) => {
|
||||
if (!avatarInfo?.SkillTrees?.[btn.id]) {
|
||||
if (!avatarSelected?.SkillTrees?.[btn.id]) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
@@ -178,13 +159,13 @@ export default function SkillsInfo() {
|
||||
${btn.size === "special" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""}
|
||||
${btn.size === "memory" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
|
||||
${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
|
||||
${skillSelected === btn.id ? "border-4 border-primary" : ""}
|
||||
${skillIDSelected === btn.id ? "border-4 border-primary" : ""}
|
||||
${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: ""}
|
||||
`}
|
||||
onClick={() => {
|
||||
setSkillSelected(btn.id === skillSelected ? null : btn.id)
|
||||
setSkillIDSelected(btn.id === skillIDSelected ? null : btn.id)
|
||||
}}
|
||||
style={{
|
||||
left: btn.left,
|
||||
@@ -193,7 +174,7 @@ export default function SkillsInfo() {
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={getImageSkill(avatarInfo?.SkillTrees?.[btn.id]?.["1"]?.Icon, avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]) || ""}
|
||||
src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`}
|
||||
alt={btn.id.replaceAll("Point", "")}
|
||||
priority={true}
|
||||
unoptimized
|
||||
@@ -201,6 +182,8 @@ export default function SkillsInfo() {
|
||||
width={124}
|
||||
height={124}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
padding: "2px",
|
||||
filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : ""
|
||||
}}
|
||||
/>
|
||||
@@ -272,7 +255,7 @@ export default function SkillsInfo() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!traceButtons && avatarInfo && (
|
||||
{!traceButtons && avatarSelected && (
|
||||
<div className="flex flex-col relative w-full aspect-square">
|
||||
|
||||
</div>
|
||||
@@ -285,7 +268,7 @@ export default function SkillsInfo() {
|
||||
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
|
||||
{transI18n("details")}
|
||||
</h2>
|
||||
{skillSelected && avatarInfo?.SkillTrees && avatarData && (
|
||||
{skillIDSelected && avatarSelected?.SkillTrees && avatarData && (
|
||||
<div>
|
||||
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
|
||||
<div>
|
||||
@@ -309,14 +292,14 @@ export default function SkillsInfo() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillSelected)?.size !== "big" ? (
|
||||
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillIDSelected)?.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) => {
|
||||
if (traceButtons?.find((btn) => btn.id === skillSelected)?.size === "special") {
|
||||
if (traceButtons?.find((btn) => btn.id === skillIDSelected)?.size === "special") {
|
||||
if (e.target.checked) {
|
||||
const newData = structuredClone(avatarData)
|
||||
newData.data.skills[skillInfo?.PointID] = 1
|
||||
@@ -343,7 +326,7 @@ export default function SkillsInfo() {
|
||||
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
|
||||
&& (
|
||||
<div className="text-xl font-bold flex items-center gap-2 mt-2">
|
||||
{skillInfo.PointName}
|
||||
{getLocaleName(locale, skillInfo.PointName)}
|
||||
{skillInfo.StatusAddList.length > 0 && (
|
||||
<div>
|
||||
{skillInfo.StatusAddList.map((status, index) => (
|
||||
@@ -360,8 +343,8 @@ export default function SkillsInfo() {
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skillInfo?.PointDesc || "",
|
||||
skillInfo?.ParamList || []
|
||||
getLocaleName(locale, skillInfo?.PointDesc) || "",
|
||||
skillInfo?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
@@ -377,19 +360,19 @@ export default function SkillsInfo() {
|
||||
<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())})`}
|
||||
{transI18n(dataLevelUpSkill.isServant ? `${skill?.AttackType ? "severaltalent" : "servantskill"}` : `${skill?.AttackType ? skill?.AttackType.toLowerCase() : "talent"}`)}
|
||||
{` (${transI18n(skill?.SkillEffect?.toLowerCase())})`}
|
||||
</div>
|
||||
|
||||
<div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(skill.Name, []) }}>
|
||||
<div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, skill.Name), []) }}>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
skill.Desc || skill.SimpleDesc,
|
||||
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.ParamList || []
|
||||
getLocaleName(locale, skill.Desc),
|
||||
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,145 +17,145 @@ export const listCurrentLanguageApi : Record<string, string> = {
|
||||
export const mappingStats = <Record<string, {name: string, icon: string, unit: string, baseStat: string}> > {
|
||||
"HPDelta": {
|
||||
name:"HP",
|
||||
icon:"/icon/hp.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
|
||||
unit: "",
|
||||
baseStat: "HP"
|
||||
},
|
||||
"AttackDelta": {
|
||||
name:"ATK",
|
||||
icon:"/icon/attack.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
|
||||
unit: "",
|
||||
baseStat: "ATK"
|
||||
},
|
||||
"HPAddedRatio": {
|
||||
name:"HP",
|
||||
icon:"/icon/hp.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
|
||||
unit: "%",
|
||||
baseStat: "HP"
|
||||
},
|
||||
"AttackAddedRatio": {
|
||||
name:"ATK",
|
||||
icon:"/icon/attack.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
|
||||
unit: "%",
|
||||
baseStat: "ATK"
|
||||
},
|
||||
"DefenceDelta": {
|
||||
name:"DEF",
|
||||
icon:"/icon/defence.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
|
||||
unit: "",
|
||||
baseStat: "DEF"
|
||||
},
|
||||
"DefenceAddedRatio": {
|
||||
name:"DEF",
|
||||
icon:"/icon/defence.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
|
||||
unit: "%",
|
||||
baseStat: "DEF"
|
||||
},
|
||||
"SpeedAddedRatio": {
|
||||
name:"SPD",
|
||||
icon:"/icon/speed.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
|
||||
unit: "%",
|
||||
baseStat: "SPD"
|
||||
},
|
||||
"BaseSpeed": {
|
||||
name:"SPD",
|
||||
icon:"/icon/speed.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
|
||||
unit: "",
|
||||
baseStat: "SPD"
|
||||
},
|
||||
"CriticalChanceBase": {
|
||||
name:"CRIT Rate",
|
||||
icon:"/icon/crit-rate.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconCriticalChance.png",
|
||||
unit: "%",
|
||||
baseStat: "CRITRate"
|
||||
},
|
||||
"CriticalDamageBase": {
|
||||
name:"CRIT DMG",
|
||||
icon:"/icon/crit-damage.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
|
||||
unit: "%",
|
||||
baseStat: "CRITDmg"
|
||||
},
|
||||
"HealRatioBase": {
|
||||
name:"Outgoing Healing Boost",
|
||||
icon:"/icon/healing-boost.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconHealRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "HealBoost"
|
||||
},
|
||||
"StatusProbabilityBase": {
|
||||
name:"Effect Hit Rate",
|
||||
icon:"/icon/effect-hit-rate.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconStatusProbability.png",
|
||||
unit: "%",
|
||||
baseStat: "EffectHitRate"
|
||||
},
|
||||
"StatusResistanceBase": {
|
||||
name:"Effect RES",
|
||||
icon:"/icon/effect-res.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconStatusResistance.png",
|
||||
unit: "%",
|
||||
baseStat: "EffectRES"
|
||||
},
|
||||
"BreakDamageAddedRatioBase": {
|
||||
name:"Break Effect",
|
||||
icon:"/icon/break-effect.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconBreakUp.png",
|
||||
unit: "%",
|
||||
baseStat: "BreakEffect"
|
||||
},
|
||||
"SpeedDelta": {
|
||||
name:"SPD",
|
||||
icon:"/icon/speed.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
|
||||
unit: "",
|
||||
baseStat: "SPD"
|
||||
},
|
||||
"PhysicalAddedRatio": {
|
||||
name:"Physical DMG Boost",
|
||||
icon:"/icon/physical-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "PhysicalAdd"
|
||||
},
|
||||
"FireAddedRatio": {
|
||||
name:"Fire DMG Boost",
|
||||
icon:"/icon/fire-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "FireAdd"
|
||||
},
|
||||
"IceAddedRatio": {
|
||||
name:"Ice DMG Boost",
|
||||
icon:"/icon/ice-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "IceAdd"
|
||||
},
|
||||
"ThunderAddedRatio": {
|
||||
name:"Thunder DMG Boost",
|
||||
icon:"/icon/thunder-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "ThunderAdd"
|
||||
},
|
||||
"WindAddedRatio": {
|
||||
name:"Wind DMG Boost",
|
||||
icon:"/icon/wind-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "WindAdd"
|
||||
},
|
||||
"QuantumAddedRatio": {
|
||||
name:"Quantum DMG Boost",
|
||||
icon:"/icon/quantum-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "QuantumAdd"
|
||||
},
|
||||
"ImaginaryAddedRatio": {
|
||||
name:"Imaginary DMG Boost",
|
||||
icon:"/icon/imaginary-add.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
|
||||
unit: "%",
|
||||
baseStat: "ImaginaryAdd"
|
||||
},
|
||||
"ElationDamageAddedRatioBase": {
|
||||
name:"Elation DMG Boost",
|
||||
icon:"/icon/IconJoy.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconJoy.png",
|
||||
unit: "%",
|
||||
baseStat: "ElationAdd"
|
||||
},
|
||||
"SPRatioBase": {
|
||||
name:"Energy Regeneration Rate",
|
||||
icon:"/icon/energy-rate.webp",
|
||||
icon:"spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
|
||||
unit: "%",
|
||||
baseStat: "EnergyRate"
|
||||
}
|
||||
@@ -169,7 +169,14 @@ export const ratioStats = [
|
||||
"SpeedAddedRatio",
|
||||
]
|
||||
|
||||
|
||||
export const mappingRelicSlot: Record<string, string> = {
|
||||
"1": "HEAD",
|
||||
"2": "HAND",
|
||||
"3": "BODY",
|
||||
"4": "FOOT",
|
||||
"5": "NECK",
|
||||
"6": "OBJECT",
|
||||
}
|
||||
|
||||
export const themeColors: Record<string, { bg: string; bgHover: string; text: string; border: string }> = {
|
||||
winter: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mappingStats, ratioStats } from "@/constant/constant"
|
||||
import { AffixDetail } from "@/types"
|
||||
import { EliteData, HardLevelData, MainAffixData, MonsterDetail, SubAffixData} from "@/types"
|
||||
|
||||
export function calcPromotion(level: number) {
|
||||
if (level < 20) {
|
||||
@@ -37,29 +37,29 @@ export function calcRarity(rarity: string) {
|
||||
return 1
|
||||
}
|
||||
|
||||
export function calcMainAffixBonus(affix?: AffixDetail, level?: number) {
|
||||
export function calcMainAffixBonus(affix?: MainAffixData, level?: number) {
|
||||
if (!affix || typeof level !== "number") return "0"
|
||||
const value = affix.base + affix.step * level;
|
||||
const value = affix.BaseValue + affix.LevelAdd * level;
|
||||
|
||||
if (mappingStats?.[affix.property].unit === "%") {
|
||||
if (mappingStats?.[affix.Property].unit === "%") {
|
||||
return (value * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.property].name === "SPD") {
|
||||
if (mappingStats?.[affix.Property].name === "SPD") {
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
return value.toFixed(0);
|
||||
}
|
||||
|
||||
export const calcAffixBonus = (affix?: AffixDetail, stepCount?: number, rollCount?: number) => {
|
||||
export const calcAffixBonus = (affix?: SubAffixData, stepCount?: number, rollCount?: number) => {
|
||||
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number") return "0"
|
||||
if (mappingStats?.[affix.property].unit === "%") {
|
||||
return ((affix.base * rollCount + affix.step * stepCount) * 100).toFixed(1);
|
||||
if (mappingStats?.[affix.Property].unit === "%") {
|
||||
return ((affix.BaseValue * rollCount + affix.StepValue * stepCount) * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.property].name === "SPD") {
|
||||
return (affix.base * rollCount + affix.step * stepCount).toFixed(1);
|
||||
if (mappingStats?.[affix.Property].name === "SPD") {
|
||||
return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(1);
|
||||
}
|
||||
return (affix.base * rollCount + affix.step * stepCount).toFixed(0);
|
||||
return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(0);
|
||||
}
|
||||
|
||||
export const calcBaseStat = (baseStat: number, stepStat: number, roundFixed: number, level: number) => {
|
||||
@@ -72,19 +72,19 @@ export const calcBaseStatRaw = (baseStat?: number, stepStat?: number, level?: nu
|
||||
return baseStat + stepStat * (level-1);
|
||||
}
|
||||
|
||||
export const calcSubAffixBonusRaw = (affix?: AffixDetail, stepCount?: number, rollCount?: number, baseStat?: number) => {
|
||||
export const calcSubAffixBonusRaw = (affix?: SubAffixData, stepCount?: number, rollCount?: number, baseStat?: number) => {
|
||||
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number" || typeof baseStat !== "number") return 0
|
||||
if (ratioStats.includes(affix.property)) {
|
||||
return (affix.base * rollCount + affix.step * stepCount) * baseStat;
|
||||
if (ratioStats.includes(affix.Property)) {
|
||||
return (affix.BaseValue * rollCount + affix.StepValue * stepCount) * baseStat;
|
||||
}
|
||||
return affix.base * rollCount + affix.step * stepCount;
|
||||
return affix.BaseValue * rollCount + affix.StepValue * stepCount;
|
||||
}
|
||||
|
||||
export const calcMainAffixBonusRaw = (affix?: AffixDetail, level?: number, baseStat?: number) => {
|
||||
export const calcMainAffixBonusRaw = (affix?: MainAffixData, level?: number, baseStat?: number) => {
|
||||
if (!affix || typeof level !== "number" || typeof baseStat !== "number") return 0
|
||||
const value = affix.base + affix.step * level;
|
||||
const value = affix.BaseValue + affix.LevelAdd * level;
|
||||
|
||||
if (ratioStats.includes(affix.property)) {
|
||||
if (ratioStats.includes(affix.Property)) {
|
||||
return baseStat * value
|
||||
}
|
||||
|
||||
@@ -97,4 +97,42 @@ export const calcBonusStatRaw = (affix?: string, baseStat?: number, bonusValue?:
|
||||
return baseStat * bonusValue
|
||||
}
|
||||
return bonusValue
|
||||
}
|
||||
|
||||
export const calcMonsterStats = (
|
||||
monster: MonsterDetail,
|
||||
eliteGroup: number,
|
||||
hardLevelGroup: number,
|
||||
level: number,
|
||||
hardLevelConfig: Record<string, Record<string, HardLevelData>>,
|
||||
eliteConfig: Record<string, EliteData>
|
||||
) => {
|
||||
let hardLevelRatio = {
|
||||
AttackRatio: 1,
|
||||
DefenceRatio:1,
|
||||
HPRatio: 1,
|
||||
SpeedRatio: 1,
|
||||
StanceRatio: 1
|
||||
}
|
||||
if (hardLevelConfig?.[hardLevelGroup.toString()]?.[level.toString()]) {
|
||||
hardLevelRatio = hardLevelConfig?.[hardLevelGroup.toString()]?.[level.toString()]
|
||||
}
|
||||
let eliteRatio = {
|
||||
AttackRatio: 1,
|
||||
DefenceRatio:1,
|
||||
HPRatio: 1,
|
||||
SpeedRatio: 1,
|
||||
StanceRatio: 1
|
||||
}
|
||||
if (eliteConfig?.[eliteGroup.toString()]) {
|
||||
eliteRatio = eliteConfig?.[eliteGroup.toString()]
|
||||
}
|
||||
|
||||
return {
|
||||
atk: monster.Base.AttackBase * monster.Modify.AttackModifyRatio * hardLevelRatio.AttackRatio * eliteRatio.AttackRatio,
|
||||
def: monster.Base.DefenceBase * monster.Modify.DefenceModifyRatio * hardLevelRatio.DefenceRatio * eliteRatio.DefenceRatio,
|
||||
hp: monster.Base.HPBase * monster.Modify.HPModifyRatio * hardLevelRatio.HPRatio * eliteRatio.HPRatio,
|
||||
spd: monster.Base.SpeedBase * monster.Modify.SpeedModifyRatio * hardLevelRatio.SpeedRatio * eliteRatio.SpeedRatio,
|
||||
stance: (monster.Base.StanceBase * monster.Modify.StanceModifyRatio * hardLevelRatio.StanceRatio * eliteRatio.StanceRatio) / 3,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, CharacterDetail, FreeSRJson, RelicStore } from "@/types";
|
||||
import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, AvatarDetail, FreeSRJson, RelicStore } from "@/types";
|
||||
|
||||
function safeNumber(val: string | number | null, fallback = 0): number {
|
||||
if (!val) return fallback;
|
||||
@@ -6,7 +6,7 @@ function safeNumber(val: string | number | null, fallback = 0): number {
|
||||
return Number.isFinite(num) && num !== 0 ? num : fallback;
|
||||
}
|
||||
|
||||
export function converterToAvatarStore(data: Record<string, CharacterDetail>): { [key: string]: AvatarStore } {
|
||||
export function converterToAvatarStore(data: Record<string, AvatarDetail>): { [key: string]: AvatarStore } {
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [
|
||||
key,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import useMazeStore from "@/stores/mazeStore";
|
||||
|
||||
import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types";
|
||||
|
||||
|
||||
@@ -11,7 +12,7 @@ export function converterToFreeSRJson(
|
||||
ce_config: CEConfigStore,
|
||||
peak_config: PEAKConfigStore,
|
||||
): FreeSRJson {
|
||||
const { SkillTree } = useMazeStore.getState()
|
||||
const { skillConfig } = useDetailDataStore.getState()
|
||||
const lightcones: LightconeJson[] = []
|
||||
const relics: RelicJson[] = []
|
||||
let battleJson: BattleConfigJson
|
||||
@@ -84,8 +85,8 @@ export function converterToFreeSRJson(
|
||||
Object.entries(avatars).forEach(([avatarId, avatar]) => {
|
||||
const skillsByAnchorType: Record<string, number> = {}
|
||||
for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) {
|
||||
if (SkillTree?.[skillId]) {
|
||||
skillsByAnchorType[SkillTree[skillId].index_slot] = level > SkillTree[skillId].max_level ? SkillTree[skillId].max_level : level
|
||||
if (skillConfig?.[skillId]) {
|
||||
skillsByAnchorType[skillConfig[skillId].IndexSlot] = level > skillConfig[skillId].MaxLevel ? skillConfig[skillId].MaxLevel : level
|
||||
}
|
||||
}
|
||||
avatarsJson[avatarId] = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { listCurrentLanguage } from "@/constant/constant";
|
||||
import { CharacterBasic, EventBasic, LightConeBasic, MonsterBasic } from "@/types";
|
||||
import { AvatarDetail } from "@/types";
|
||||
import { useTranslations } from "next-intl"
|
||||
|
||||
type TFunc = ReturnType<typeof useTranslations>
|
||||
@@ -7,7 +7,7 @@ type TFunc = ReturnType<typeof useTranslations>
|
||||
export function getNameChar(
|
||||
locale: string,
|
||||
t: TFunc,
|
||||
data: CharacterBasic | undefined
|
||||
data: AvatarDetail | undefined
|
||||
): string {
|
||||
if (!data) return "";
|
||||
|
||||
@@ -17,20 +17,20 @@ export function getNameChar(
|
||||
|
||||
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
||||
|
||||
let text = data.lang[langKey] ?? "";
|
||||
let text = data.Name[langKey] ?? "";
|
||||
|
||||
if (!text) {
|
||||
text = data.lang["en"] ?? "";
|
||||
text = data.Name["en"] ?? "";
|
||||
}
|
||||
|
||||
if (Number(data.id) > 8000) {
|
||||
text = `${t("trailblazer")} • ${t(data?.baseType?.toLowerCase() ?? "")}`;
|
||||
if (data.ID > 8000) {
|
||||
text = `${t("trailblazer")} • ${t(data?.BaseType?.toLowerCase() ?? "")}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
export function getLocaleName(locale: string, data: LightConeBasic | EventBasic | MonsterBasic | undefined): string {
|
||||
export function getLocaleName(locale: string, data: Record<string, string> | undefined | null): string {
|
||||
if (!data) {
|
||||
return ""
|
||||
}
|
||||
@@ -41,10 +41,10 @@ export function getLocaleName(locale: string, data: LightConeBasic | EventBasic
|
||||
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
||||
|
||||
|
||||
let text = data.lang[langKey] ?? "";
|
||||
let text = data[langKey] ?? "";
|
||||
|
||||
if (!text) {
|
||||
text = data.lang["en"] ?? "";
|
||||
text = data["en"] ?? "";
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
|
||||
export function getSkillTree(enhanced: string) {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore.getState()
|
||||
import { AvatarDetail } from "@/types";
|
||||
|
||||
export function getSkillTree(avatarSelected: AvatarDetail | null, enhanced: string) {
|
||||
if (!avatarSelected) return null;
|
||||
if (enhanced != "") return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.Enhanced[enhanced].SkillTrees || {}).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
if (enhanced != "" && !!avatarSelected?.Enhanced?.[enhanced]?.SkillTrees) {
|
||||
return Object.values(avatarSelected?.Enhanced?.[enhanced]?.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
}
|
||||
|
||||
return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
return Object.values(avatarSelected?.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
|
||||
@@ -1,84 +1,41 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { AffixDetail, ASDetail, ChangelogItemType, CharacterBasic, CharacterDetail, ConfigMaze, EventBasic, FreeSRJson, LightConeBasic, LightConeDetail, MocDetail, MonsterBasic, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
|
||||
import { ASGroupDetail, ChangelogItemType, AvatarDetail, FreeSRJson, LightConeDetail, MOCGroupDetail, MonsterDetail, PeakGroupDetail, PFGroupDetail, PSResponse, RelicSetDetail } from "@/types";
|
||||
import axios from 'axios';
|
||||
import { psResponseSchema } from "@/zod";
|
||||
import { ExtraData } from "@/types";
|
||||
import { ExtraData, Metadata } from "@/types";
|
||||
|
||||
export async function getConfigMazeApi(): Promise<ConfigMaze> {
|
||||
export async function getMetadataApi(): Promise<Metadata> {
|
||||
try {
|
||||
const res = await axios.get<ConfigMaze>(`/data/config_maze.json`);
|
||||
return res.data as ConfigMaze;
|
||||
const res = await axios.get<Metadata>(`/api/data/metadata`);
|
||||
return res.data as Metadata;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||
} else {
|
||||
console.log(`Unexpected error: ${String(error)}`);
|
||||
}
|
||||
console.error('Failed to fetch metadata:', error);
|
||||
return {
|
||||
Avatar: {},
|
||||
MOC: {},
|
||||
AS: {},
|
||||
PF: {},
|
||||
BaseType: {},
|
||||
DamageType: {},
|
||||
MainAffix: {},
|
||||
SubAffix: {},
|
||||
SkillConfig: {},
|
||||
Stage: {},
|
||||
Skill: {}
|
||||
HardLevelConfig: {},
|
||||
EliteConfig: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMainAffixApi(): Promise<Record<string, Record<string, AffixDetail>>> {
|
||||
export async function getAvatarListApi(): Promise<Record<string, AvatarDetail>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, Record<string, AffixDetail>>>(`/data/main_affixes.json`);
|
||||
return res.data as Record<string, Record<string, AffixDetail>>;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||
} else {
|
||||
console.log(`Unexpected error: ${String(error)}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubAffixApi(): Promise<Record<string, Record<string, AffixDetail>>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, Record<string, AffixDetail>>>(`/data/sub_affixes.json`);
|
||||
|
||||
return res.data as Record<string, Record<string, AffixDetail>>;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||
} else {
|
||||
console.log(`Unexpected error: ${String(error)}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchCharactersApi(locale: string): Promise<Record<string, CharacterDetail>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, CharacterDetail>>(`/data/characters.${locale}.json`);
|
||||
const resIcon = await axios.get<Record<string, string[]>>(`/data/rank_icon.json`);
|
||||
for (const [key, char] of Object.entries(res.data)) {
|
||||
if (resIcon.data[key]) {
|
||||
char.RankIcon = resIcon.data[key];
|
||||
}
|
||||
}
|
||||
const res = await axios.get<Record<string, AvatarDetail>>(`/api/data/avatar`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch characters:', error);
|
||||
console.error('Failed to fetch Avatars:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchLightconesApi(locale: string): Promise<Record<string, LightConeDetail>> {
|
||||
export async function getLightconeListApi(): Promise<Record<string, LightConeDetail>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, LightConeDetail>>(`/data/lightcones.${locale}.json`);
|
||||
const resBonus = await axios.get<Record<string, Record<string, { type: string, value: number }[]>>>('/data/lightcone_bonus.json');
|
||||
for (const [key, relic] of Object.entries(res.data)) {
|
||||
if (resBonus.data[key]) {
|
||||
relic.Bonus = resBonus.data[key];
|
||||
}
|
||||
}
|
||||
const res = await axios.get<Record<string, LightConeDetail>>(`/api/data/lightcone`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch lightcones:', error);
|
||||
@@ -86,15 +43,9 @@ export async function fetchLightconesApi(locale: string): Promise<Record<string,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRelicsApi(locale: string): Promise<Record<string, RelicDetail>> {
|
||||
export async function getRelicSetListApi(): Promise<Record<string, RelicSetDetail>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, RelicDetail>>(`/data/relics.${locale}.json`);
|
||||
const resBonus = await axios.get<Record<string, Record<string, { type: string, value: number }[]>>>('/data/relic_bonus.json');
|
||||
for (const [key, relic] of Object.entries(res.data)) {
|
||||
if (resBonus.data[key]) {
|
||||
relic.Bonus = resBonus.data[key];
|
||||
}
|
||||
}
|
||||
const res = await axios.get<Record<string, RelicSetDetail>>(`/api/data/relic`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch relics:', error);
|
||||
@@ -102,130 +53,66 @@ export async function fetchRelicsApi(locale: string): Promise<Record<string, Rel
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchASEventApi(locale: string): Promise<Record<string, ASDetail> | null> {
|
||||
export async function getMonsterListApi(): Promise<Record<string, MonsterDetail>> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, ASDetail>>(`/data/as.${locale}.json`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch AS:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPFEventApi(locale: string): Promise<Record<string, PFDetail> | null> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, PFDetail>>(`/data/pf.${locale}.json`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch PF:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMOCEventApi(locale: string): Promise<Record<string, MocDetail[]> | null> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, MocDetail[]>>(`/data/moc.${locale}.json`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch MOC:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPeakEventApi(locale: string): Promise<Record<string, PeakDetail> | null> {
|
||||
try {
|
||||
const res = await axios.get<Record<string, PeakDetail>>(`/data/peak.${locale}.json`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch peak:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChangelog(): Promise<ChangelogItemType[] | null> {
|
||||
try {
|
||||
const res = await axios.get<ChangelogItemType[]>(`/data/changelog.json`);
|
||||
const res = await axios.get<Record<string, MonsterDetail>>(`/api/data/monster`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch monster:', error);
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCharacterListApi(): Promise<CharacterBasic[]> {
|
||||
export async function getASEventListApi(): Promise<Record<string, ASGroupDetail>> {
|
||||
try {
|
||||
const res = await axios.get<CharacterBasic[]>('/data/character.json');
|
||||
const res = await axios.get<Record<string, ASGroupDetail>>(`/api/data/as`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch character list:', error);
|
||||
return [];
|
||||
console.error('Failed to fetch AS:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getLightconeListApi(): Promise<LightConeBasic[]> {
|
||||
export async function getPFEventListApi(): Promise<Record<string, PFGroupDetail>> {
|
||||
try {
|
||||
const res = await axios.get<LightConeBasic[]>('/data/lightcone.json');
|
||||
return res.data
|
||||
const res = await axios.get<Record<string, PFGroupDetail>>(`/api/data/pf`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch lightcone list:', error);
|
||||
return [];
|
||||
console.error('Failed to fetch PF:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getMOCEventListApi(): Promise<EventBasic[]> {
|
||||
export async function getMOCEventListApi(): Promise<Record<string, MOCGroupDetail>> {
|
||||
try {
|
||||
const res = await axios.get<EventBasic[]>('/data/moc.json');
|
||||
return res.data
|
||||
const res = await axios.get<Record<string, MOCGroupDetail>>(`/api/data/moc`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch moc list:', error);
|
||||
return [];
|
||||
console.error('Failed to fetch MOC:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getASEventListApi(): Promise<EventBasic[]> {
|
||||
export async function getPeakEventListApi(): Promise<Record<string, PeakGroupDetail>> {
|
||||
try {
|
||||
const res = await axios.get<EventBasic[]>('/data/as.json');
|
||||
return res.data
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to fetch as list:', error);
|
||||
return [];
|
||||
const res = await axios.get<Record<string, PeakGroupDetail>>(`/api/data/peak`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch peak:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPFEventListApi(): Promise<EventBasic[]> {
|
||||
export async function getChangelog(): Promise<ChangelogItemType[]> {
|
||||
try {
|
||||
const res = await axios.get<EventBasic[]>('/data/pf.json');
|
||||
return res.data
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to fetch pf list:', error);
|
||||
const res = await axios.get<ChangelogItemType[]>(`/api/data/changelog`);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch monster:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPEAKEventListApi(): Promise<EventBasic[]> {
|
||||
try {
|
||||
const res = await axios.get<EventBasic[]>('/data/peak.json');
|
||||
return res.data
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to fetch peak list:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMonsterListApi(): Promise<MonsterBasic[]> {
|
||||
try {
|
||||
const res = await axios.get<MonsterBasic[]>('/data/monster.json');
|
||||
return res.data
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to fetch peak list:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function SendDataToServer(
|
||||
username: string,
|
||||
password: string,
|
||||
|
||||
38
src/lib/cache/cache.ts
vendored
Normal file
38
src/lib/cache/cache.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { readFileSync, readdirSync } from "fs"
|
||||
import path from "path"
|
||||
|
||||
type CacheItem = {
|
||||
buf: Uint8Array
|
||||
type: "json" | "br"
|
||||
}
|
||||
|
||||
const cache = new Map<string, CacheItem>()
|
||||
|
||||
const dir = path.join(process.cwd(), "data")
|
||||
|
||||
for (const f of readdirSync(dir)) {
|
||||
const file = path.join(dir, f)
|
||||
|
||||
if (f.endsWith(".json.br")) {
|
||||
const name = f.replace(".json.br", "")
|
||||
const buf = new Uint8Array(readFileSync(file))
|
||||
cache.set(name, {
|
||||
buf,
|
||||
type: "br"
|
||||
})
|
||||
}
|
||||
|
||||
if (f.endsWith(".json")) {
|
||||
const name = f.replace(".json", "")
|
||||
const buf = new Uint8Array(readFileSync(file))
|
||||
|
||||
cache.set(name, {
|
||||
buf,
|
||||
type: "json"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function getDataCache(name: string) {
|
||||
return cache.get(name)
|
||||
}
|
||||
1
src/lib/cache/index.ts
vendored
Normal file
1
src/lib/cache/index.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./cache"
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from "./useFetchConfigData";
|
||||
export * from "./useFetchMetaData";
|
||||
export * from "./useFetchAvatarData";
|
||||
export * from "./useFetchLightconeData";
|
||||
export * from "./useFetchRelicData";
|
||||
|
||||
@@ -1,72 +1,26 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchASEventApi, getASEventListApi } from '@/lib/api'
|
||||
import { getASEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
import { EventStageDetail } from '@/types'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
|
||||
export const useFetchASData = () => {
|
||||
const { setASEvent, setMapASInfo } = useEventStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataAS, error: errorAS } = useQuery({
|
||||
queryKey: ['asData'],
|
||||
export const useFetchASGroupData = () => {
|
||||
const { setMapAS } = useDetailDataStore()
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['ASGroupData'],
|
||||
queryFn: getASEventListApi,
|
||||
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { data: dataASInfo, error: errorASInfo } = useQuery({
|
||||
queryKey: ['asInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchASEventApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
enabled: !!dataAS,
|
||||
select: (data) => {
|
||||
const newData = { ...data }
|
||||
for (const key in newData) {
|
||||
for (const item of newData[key].Level) {
|
||||
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
|
||||
MonsterList: event.MonsterList.map((monster) => {
|
||||
const { $type, ...rest } = monster;
|
||||
return rest;
|
||||
})
|
||||
}))
|
||||
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
|
||||
MonsterList: event.MonsterList.map((monster) => {
|
||||
const { $type, ...rest } = monster;
|
||||
return rest;
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
return newData
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataAS && !errorAS) {
|
||||
setASEvent(dataAS)
|
||||
} else if (errorAS) {
|
||||
toast.error("Failed to load AS data")
|
||||
if (query.data) {
|
||||
setMapAS(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load ASGroup data")
|
||||
}
|
||||
}, [dataAS, errorAS, setASEvent])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataASInfo && !errorASInfo) {
|
||||
setMapASInfo(dataASInfo)
|
||||
} else if (errorASInfo) {
|
||||
toast.error("Failed to load AS info data")
|
||||
}
|
||||
|
||||
}, [dataASInfo, errorASInfo, setMapASInfo])
|
||||
}, [query.data, query.error, setMapAS])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,63 +1,43 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchCharactersApi, getCharacterListApi } from '@/lib/api'
|
||||
import { getAvatarListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import useAvatarStore from '@/stores/avatarStore'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import useCurrentDataStore from '@/stores/currentDataStore'
|
||||
import useUserDataStore from '@/stores/userDataStore'
|
||||
import { converterToAvatarStore } from '@/helper'
|
||||
|
||||
export const useFetchAvatarData = () => {
|
||||
const { setAvatar, avatars } = useUserDataStore()
|
||||
const { setListAvatar, setAllMapAvatarInfo, mapAvatarInfo, setAvatarSelected, avatarSelected } = useAvatarStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataAvatar, error: errorAvatar } = useQuery({
|
||||
queryKey: ['avatarData'],
|
||||
queryFn: getCharacterListApi,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { setMapAvatar, mapAvatar } = useDetailDataStore()
|
||||
const { setAvatar, avatars } = useUserDataStore()
|
||||
const { avatarSelected, setAvatarSelected } = useCurrentDataStore()
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['AvatarData'],
|
||||
queryFn: getAvatarListApi,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
useEffect(() => {
|
||||
const listAvatarId = Object.keys(avatars)
|
||||
const listAvatarNotExist = Object.entries(mapAvatarInfo).filter(([avatarId]) => !listAvatarId.includes(avatarId))
|
||||
const listAvatarNotExist = Object.entries(mapAvatar).filter(([avatarId]) => !listAvatarId.includes(avatarId))
|
||||
const avatarStore = converterToAvatarStore(Object.fromEntries(listAvatarNotExist))
|
||||
if (Object.keys(avatarStore).length === 0) return
|
||||
for (const avatar of Object.values(avatarStore)) {
|
||||
setAvatar(avatar)
|
||||
}
|
||||
}, [mapAvatarInfo])
|
||||
|
||||
const { data: dataAvatarInfo, error: errorAvatarInfo } = useQuery({
|
||||
queryKey: ['avatarInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchCharactersApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
enabled: !!dataAvatar,
|
||||
});
|
||||
}, [mapAvatar])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataAvatar && !errorAvatar) {
|
||||
setListAvatar(dataAvatar)
|
||||
if (!avatarSelected) {
|
||||
setAvatarSelected(dataAvatar[0])
|
||||
}
|
||||
|
||||
} else if (errorAvatar) {
|
||||
toast.error("Failed to load avatar data")
|
||||
useEffect(() => {
|
||||
if (query.data) {
|
||||
setMapAvatar(query.data)
|
||||
if (!avatarSelected) {
|
||||
setAvatarSelected(Object.values(query.data)[0])
|
||||
}
|
||||
}, [dataAvatar, errorAvatar])
|
||||
}
|
||||
if (query.error) toast.error("Failed to load Avatar data")
|
||||
}, [query.data, query.error, setMapAvatar, avatarSelected, setAvatarSelected])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataAvatarInfo && !errorAvatarInfo) {
|
||||
setAllMapAvatarInfo(dataAvatarInfo)
|
||||
} else if (errorAvatarInfo) {
|
||||
toast.error("Failed to load avatar info data")
|
||||
}
|
||||
}, [dataAvatarInfo, errorAvatarInfo])
|
||||
}
|
||||
return query
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchChangelog } from '@/lib/api'
|
||||
import { getChangelog } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import useModelStore from '@/stores/modelStore'
|
||||
@@ -9,23 +9,24 @@ import useLocaleStore from '@/stores/localeStore'
|
||||
export const useFetchChangelog = () => {
|
||||
const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore()
|
||||
const { setIsChangelog } = useModelStore()
|
||||
const { data: dataChangelog, error: errorChangelog } = useQuery({
|
||||
const query = useQuery({
|
||||
queryKey: ['changelog'],
|
||||
queryFn: fetchChangelog,
|
||||
queryFn: getChangelog,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (dataChangelog && !errorChangelog) {
|
||||
setChangelog(dataChangelog)
|
||||
if (dataChangelog?.[0] && dataChangelog[0].version != currentVersion) {
|
||||
if (query.data && !query.error) {
|
||||
setChangelog(query.data)
|
||||
if (query.data?.[0] && query.data[0].version != currentVersion) {
|
||||
setIsChangelog(true)
|
||||
setCurrentVersion(dataChangelog[0].version)
|
||||
setCurrentVersion(query.data[0].version)
|
||||
}
|
||||
} else if (errorChangelog) {
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load changelog data")
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataChangelog, errorChangelog, setChangelog, setCurrentVersion, setIsChangelog])
|
||||
}, [query.data, query.error, setChangelog, setCurrentVersion, setIsChangelog])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getConfigMazeApi, getMainAffixApi, getSubAffixApi } from '@/lib/api'
|
||||
import useAffixStore from '@/stores/affixStore'
|
||||
import useMazeStore from '@/stores/mazeStore'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
export const useFetchConfigData = () => {
|
||||
|
||||
const { setMapMainAffix, setMapSubAffix } = useAffixStore()
|
||||
const { setAllMazeData } = useMazeStore()
|
||||
|
||||
const { data, error } = useQuery({
|
||||
queryKey: ['initialConfigData'],
|
||||
queryFn: async () => {
|
||||
const [maze, main, sub] = await Promise.all([
|
||||
getConfigMazeApi(),
|
||||
getMainAffixApi(),
|
||||
getSubAffixApi(),
|
||||
])
|
||||
return { maze, main, sub }
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data && !error) {
|
||||
setAllMazeData(data.maze)
|
||||
setMapMainAffix(data.main)
|
||||
setMapSubAffix(data.sub)
|
||||
}
|
||||
else if (error) {
|
||||
toast.error("Failed to load initial config data")
|
||||
}
|
||||
}, [data, error, setAllMazeData, setMapMainAffix, setMapSubAffix])
|
||||
}
|
||||
@@ -1,46 +1,25 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchLightconesApi, getLightconeListApi } from '@/lib/api'
|
||||
import { getLightconeListApi } from '@/lib/api';
|
||||
import { useEffect } from 'react'
|
||||
import useLightconeStore from '@/stores/lightconeStore'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useDetailDataStore from '@/stores/detailDataStore';
|
||||
|
||||
export const useFetchLightconeData = () => {
|
||||
const { setListLightcone, setAllMapLightconeInfo } = useLightconeStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataLightcone, error: errorLightcone } = useQuery({
|
||||
const { setMapLightCone } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['lightconeData'],
|
||||
queryFn: getLightconeListApi,
|
||||
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { data: dataLightconeInfo, error: errorLightconeInfo } = useQuery({
|
||||
queryKey: ['lightconeInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchLightconesApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
enabled: !!dataLightcone,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataLightcone && !errorLightcone) {
|
||||
setListLightcone(dataLightcone)
|
||||
} else if (errorLightcone) {
|
||||
if (query.data && !query.error) {
|
||||
setMapLightCone(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load lightcone data")
|
||||
}
|
||||
}, [dataLightcone, errorLightcone, setListLightcone])
|
||||
}, [query.data, query.error, setMapLightCone])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataLightconeInfo && !errorLightconeInfo) {
|
||||
setAllMapLightconeInfo(dataLightconeInfo)
|
||||
} else if (errorLightconeInfo) {
|
||||
toast.error("Failed to load lightcone info data")
|
||||
}
|
||||
|
||||
}, [dataLightconeInfo, errorLightconeInfo, setAllMapLightconeInfo])
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,66 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchMOCEventApi, getMOCEventListApi } from '@/lib/api'
|
||||
import { getMOCEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
import { EventStageDetail, MocDetail } from '@/types'
|
||||
|
||||
export const useFetchMOCData = () => {
|
||||
const { setMOCEvent, setMapMOCInfo } = useEventStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataMOC, error: errorMOC } = useQuery({
|
||||
queryKey: ['mocData'],
|
||||
export const useFetchMOCGroupData = () => {
|
||||
const { setMapMoc } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['MOCGroupData'],
|
||||
queryFn: getMOCEventListApi,
|
||||
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { data: dataMOCInfo, error: errorMOCInfo } = useQuery({
|
||||
queryKey: ['mocInfoData', locale],
|
||||
queryFn: async () => {
|
||||
const result = await fetchMOCEventApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
);
|
||||
return result;
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
select: (data) => {
|
||||
const newData = { ...data }
|
||||
for (const key in newData) {
|
||||
for (const item of newData[key]) {
|
||||
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
}
|
||||
}
|
||||
return newData
|
||||
},
|
||||
enabled: !!dataMOC,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataMOC && !errorMOC) {
|
||||
setMOCEvent(dataMOC)
|
||||
} else if (errorMOC) {
|
||||
toast.error("Failed to load MOC data")
|
||||
if (query.data && !query.error) {
|
||||
setMapMoc(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load MOCGroup data")
|
||||
}
|
||||
}, [dataMOC, errorMOC, setMOCEvent])
|
||||
}, [query.data, query.error, setMapMoc])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataMOCInfo && !errorMOCInfo) {
|
||||
setMapMOCInfo(dataMOCInfo as Record<string, MocDetail[]>)
|
||||
} else if (errorMOCInfo) {
|
||||
toast.error("Failed to load MOC info data")
|
||||
}
|
||||
|
||||
}, [dataMOCInfo, errorMOCInfo, setMapMOCInfo])
|
||||
return query
|
||||
}
|
||||
|
||||
33
src/lib/hooks/useFetchMetaData.ts
Normal file
33
src/lib/hooks/useFetchMetaData.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { getMetadataApi } from '@/lib/api'
|
||||
import useCurrentDataStore from '@/stores/currentDataStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
|
||||
export const useFetchConfigData = () => {
|
||||
const { setMetaData } = useDetailDataStore()
|
||||
const { setSettingData } = useCurrentDataStore()
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['initialConfigData'],
|
||||
queryFn: async () => {
|
||||
const metaData = await getMetadataApi()
|
||||
return { metaData }
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (query.data && !query.error) {
|
||||
setSettingData(query.data.metaData)
|
||||
setMetaData(query.data.metaData)
|
||||
}
|
||||
else if (query.error) {
|
||||
toast.error("Failed to load initial config data")
|
||||
}
|
||||
}, [query.data, query.error, setMetaData, setSettingData])
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -2,28 +2,24 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getMonsterListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useMonsterStore from '@/stores/monsterStore'
|
||||
import { MonsterBasic } from '@/types'
|
||||
|
||||
export const useFetchMonsterData = () => {
|
||||
const { setAllMapMonster, setListMonster } = useMonsterStore()
|
||||
const { data: dataMonster, error: errorMonster } = useQuery({
|
||||
queryKey: ['monsterData'],
|
||||
const { setMapMonster } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['MonsterData'],
|
||||
queryFn: getMonsterListApi,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (dataMonster && !errorMonster) {
|
||||
setListMonster(dataMonster.sort((a, b) => Number(b.id) - Number(a.id)))
|
||||
const monsterMap = dataMonster.reduce<Record<string, MonsterBasic>>((acc, m) => {
|
||||
acc[m.id] = m
|
||||
return acc
|
||||
}, {})
|
||||
setAllMapMonster(monsterMap)
|
||||
} else if (errorMonster) {
|
||||
toast.error("Failed to load monster data")
|
||||
if (query.data && !query.error) {
|
||||
setMapMonster(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load Monster data")
|
||||
}
|
||||
}, [dataMonster, errorMonster, setAllMapMonster, setListMonster])
|
||||
}, [query.data, query.error, setMapMonster])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,68 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchPeakEventApi, getPEAKEventListApi } from '@/lib/api'
|
||||
import { getPeakEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
import { EventStageDetail, PeakDetail } from '@/types'
|
||||
|
||||
export const useFetchPEAKData = () => {
|
||||
const { setPEAKEvent, setMapPEAKInfo } = useEventStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataPEAK, error: errorPEAK } = useQuery({
|
||||
queryKey: ['peakData'],
|
||||
queryFn: getPEAKEventListApi,
|
||||
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
|
||||
export const useFetchPeakGroupData = () => {
|
||||
const { setMapPeak } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['PeakGroupData'],
|
||||
queryFn: getPeakEventListApi,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { data: dataPEAKInfo, error: errorPEAKInfo } = useQuery({
|
||||
queryKey: ['peakInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchPeakEventApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
select: (data) => {
|
||||
const newData = { ...data }
|
||||
for (const key in newData) {
|
||||
for (const item of newData[key].PreLevel) {
|
||||
item.EventIDList = item.EventIDList.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
}
|
||||
newData[key].BossLevel.EventIDList = newData[key].BossLevel.EventIDList.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
newData[key].BossConfig.EventIDList = newData[key].BossConfig.EventIDList.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
}
|
||||
return newData
|
||||
},
|
||||
enabled: !!dataPEAK,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataPEAK && !errorPEAK) {
|
||||
setPEAKEvent(dataPEAK)
|
||||
} else if (errorPEAK) {
|
||||
toast.error("Failed to load PEAK data")
|
||||
if (query.data && !query.error) {
|
||||
setMapPeak(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load PeakGroup data")
|
||||
}
|
||||
}, [dataPEAK, errorPEAK, setPEAKEvent])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataPEAKInfo && !errorPEAKInfo) {
|
||||
setMapPEAKInfo(dataPEAKInfo as Record<string, PeakDetail>)
|
||||
} else if (errorPEAKInfo) {
|
||||
toast.error("Failed to load PEAK info data")
|
||||
}
|
||||
|
||||
}, [dataPEAKInfo, errorPEAKInfo, setMapPEAKInfo])
|
||||
}, [query.data, query.error, setMapPeak])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,64 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchPFEventApi, getPFEventListApi } from '@/lib/api'
|
||||
import { getPFEventListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import useEventStore from '@/stores/eventStore'
|
||||
import { EventStageDetail } from '@/types'
|
||||
|
||||
export const useFetchPFData = () => {
|
||||
const { setPFEvent, setMapPFInfo } = useEventStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const { data: dataPF, error: errorPF } = useQuery({
|
||||
queryKey: ['pfData'],
|
||||
export const useFetchPFGroupData = () => {
|
||||
const { setMapPF } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['PFGroupData'],
|
||||
queryFn: getPFEventListApi,
|
||||
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
})
|
||||
|
||||
const { data: dataPFInfo, error: errorPFInfo } = useQuery({
|
||||
queryKey: ['pfInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchPFEventApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
enabled: !!dataPF,
|
||||
select: (data) => {
|
||||
const newData = { ...data }
|
||||
for (const key in newData) {
|
||||
for (const item of newData[key].Level) {
|
||||
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
|
||||
...event,
|
||||
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
|
||||
}))
|
||||
}
|
||||
}
|
||||
return newData
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataPF && !errorPF) {
|
||||
setPFEvent(dataPF)
|
||||
} else if (errorPF) {
|
||||
toast.error("Failed to load PF data")
|
||||
if (query.data && !query.error) {
|
||||
setMapPF(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load PFGroup data")
|
||||
}
|
||||
}, [dataPF, errorPF, setPFEvent])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataPFInfo && !errorPFInfo) {
|
||||
setMapPFInfo(dataPFInfo)
|
||||
} else if (errorPFInfo) {
|
||||
toast.error("Failed to load PF info data")
|
||||
}
|
||||
|
||||
}, [dataPFInfo, errorPFInfo, setMapPFInfo])
|
||||
}, [query.data, query.error, setMapPF])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
"use client"
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchRelicsApi } from '@/lib/api'
|
||||
import { getRelicSetListApi } from '@/lib/api'
|
||||
import { useEffect } from 'react'
|
||||
import useRelicStore from '@/stores/relicStore'
|
||||
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||
import useLocaleStore from '@/stores/localeStore'
|
||||
import useDetailDataStore from '@/stores/detailDataStore'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
export const useFetchRelicData = () => {
|
||||
const { setAllMapRelicInfo } = useRelicStore()
|
||||
const { locale } = useLocaleStore()
|
||||
|
||||
const { data: dataRelicInfo, error: errorRelicInfo } = useQuery({
|
||||
queryKey: ['relicInfoData', locale],
|
||||
queryFn: () =>
|
||||
fetchRelicsApi(
|
||||
listCurrentLanguageApi[locale.toLowerCase()]
|
||||
),
|
||||
export const useFetchRelicSetData = () => {
|
||||
const { setMapRelicSet } = useDetailDataStore()
|
||||
const query = useQuery({
|
||||
queryKey: ['RelicSetData'],
|
||||
queryFn: getRelicSetListApi,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (dataRelicInfo && !errorRelicInfo) {
|
||||
setAllMapRelicInfo(dataRelicInfo)
|
||||
} else if (errorRelicInfo) {
|
||||
toast.error("Failed to load relic info data")
|
||||
if (query.data && !query.error) {
|
||||
setMapRelicSet(query.data)
|
||||
} else if (query.error) {
|
||||
toast.error("Failed to load RelicSet data")
|
||||
}
|
||||
|
||||
}, [dataRelicInfo, errorRelicInfo, setAllMapRelicInfo])
|
||||
}, [query.data, query.error, setMapRelicSet])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { AffixDetail } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface AffixState {
|
||||
mapMainAffix: Record<string, Record<string, AffixDetail>>;
|
||||
mapSubAffix: Record<string, Record<string, AffixDetail>>;
|
||||
setMapMainAffix: (newMainAffix: Record<string, Record<string, AffixDetail>>) => void;
|
||||
setMapSubAffix: (newSubAffix: Record<string, Record<string, AffixDetail>>) => void;
|
||||
}
|
||||
|
||||
const useAffixStore = create<AffixState>((set) => ({
|
||||
mapMainAffix: {},
|
||||
mapSubAffix: {},
|
||||
setMapMainAffix: (newMainAffix: Record<string, Record<string, AffixDetail>>) => set({ mapMainAffix: newMainAffix }),
|
||||
setMapSubAffix: (newSubAffix: Record<string, Record<string, AffixDetail>>) => set({ mapSubAffix: newSubAffix }),
|
||||
}));
|
||||
|
||||
export default useAffixStore;
|
||||
@@ -1,76 +0,0 @@
|
||||
import { CharacterBasic, CharacterDetail, FilterAvatarType } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
|
||||
interface AvatarState {
|
||||
listAvatar: CharacterBasic[];
|
||||
listRawAvatar: CharacterBasic[];
|
||||
filter: FilterAvatarType;
|
||||
avatarSelected: CharacterBasic | null;
|
||||
mapAvatarInfo: Record<string, CharacterDetail>;
|
||||
skillSelected: string | null;
|
||||
listElement: Record<string, boolean>;
|
||||
listPath: Record<string, boolean>;
|
||||
setListElement: (newListElement: Record<string, boolean>) => void;
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
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) => ({
|
||||
listAvatar: [],
|
||||
listRawAvatar: [],
|
||||
filter: {
|
||||
name: "",
|
||||
path: [],
|
||||
element: [],
|
||||
rarity: [],
|
||||
locale: "",
|
||||
},
|
||||
avatarSelected: null,
|
||||
skillSelected: null,
|
||||
mapAvatarInfo: {},
|
||||
listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, elation: false },
|
||||
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }),
|
||||
setListAvatar: (newListAvatar: CharacterBasic[]) => set({ listAvatar: newListAvatar, listRawAvatar: newListAvatar }),
|
||||
setAvatarSelected: (newAvatarSelected: CharacterBasic) => set({ avatarSelected: newAvatarSelected }),
|
||||
setFilter: (newFilter: FilterAvatarType) => {
|
||||
set({ filter: newFilter })
|
||||
if (newFilter.locale === "") {
|
||||
return
|
||||
}
|
||||
let filteredList = get().listRawAvatar;
|
||||
if (newFilter.name) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return avatar.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.path.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.path.some((path) => avatar.baseType?.toLowerCase().includes(path.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.element.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.element.some((element) => avatar.damageType?.toLowerCase().includes(element.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.rarity.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.rarity.some((rarity) => avatar.rank?.toLowerCase().includes(rarity.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
set({ listAvatar: filteredList });
|
||||
},
|
||||
setMapAvatarInfo: (avatarId: string, newCharacter: CharacterDetail) => set((state) => ({ mapAvatarInfo: { ...state.mapAvatarInfo, [avatarId]: newCharacter } })),
|
||||
setAllMapAvatarInfo: (newCharacter: Record<string, CharacterDetail>) => set((state) => ({ mapAvatarInfo: { ...state.mapAvatarInfo, ...newCharacter } })),
|
||||
}));
|
||||
|
||||
export default useAvatarStore;
|
||||
@@ -1,12 +1,9 @@
|
||||
import { AvatarProfileCardType, CharacterBasic, FilterAvatarType } from '@/types';
|
||||
import { AvatarDetail, AvatarProfileCardType, BaseTypeData, DamageTypeData } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface CopyProfileState {
|
||||
selectedProfiles: AvatarProfileCardType[];
|
||||
listCopyAvatar: CharacterBasic[];
|
||||
listRawCopyAvatar: CharacterBasic[];
|
||||
filterCopy: FilterAvatarType;
|
||||
avatarCopySelected: CharacterBasic | null;
|
||||
avatarCopySelected: AvatarDetail | null;
|
||||
listElement: Record<string, boolean>;
|
||||
listPath: Record<string, boolean>;
|
||||
listRank: Record<string, boolean>;
|
||||
@@ -14,60 +11,31 @@ interface CopyProfileState {
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
setListRank: (newListRank: Record<string, boolean>) => void;
|
||||
setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => void;
|
||||
setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => void;
|
||||
setFilterCopy: (newFilter: FilterAvatarType) => void;
|
||||
setListCopyAvatar: (newListAvatar: CharacterBasic[]) => void;
|
||||
setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => void;
|
||||
setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => void;
|
||||
}
|
||||
|
||||
const useCopyProfileStore = create<CopyProfileState>((set, get) => ({
|
||||
const useCopyProfileStore = create<CopyProfileState>((set) => ({
|
||||
selectedProfiles: [],
|
||||
avatarCopySelected: null,
|
||||
listElement: {},
|
||||
listPath: {},
|
||||
listRank: { "4": false, "5": false },
|
||||
listCopyAvatar: [],
|
||||
listRawCopyAvatar: [],
|
||||
filterCopy: {
|
||||
name: "",
|
||||
path: [],
|
||||
element: [],
|
||||
rarity: [],
|
||||
locale: "",
|
||||
},
|
||||
avatarCopySelected: null,
|
||||
listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false },
|
||||
listRank: { "4": false, "5": false },
|
||||
|
||||
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
|
||||
setListCopyAvatar: (newListAvatar: CharacterBasic[]) => set({ listCopyAvatar: newListAvatar, listRawCopyAvatar: newListAvatar }),
|
||||
setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => set({ avatarCopySelected: newAvatarSelected }),
|
||||
setFilterCopy: (newFilter: FilterAvatarType) => {
|
||||
set({ filterCopy: newFilter })
|
||||
if (newFilter.locale === "") {
|
||||
return
|
||||
}
|
||||
let filteredList = get().listRawCopyAvatar;
|
||||
if (newFilter.name) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return avatar.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.path.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.path.some((path) => avatar.baseType?.toLowerCase().includes(path.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.element.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.element.some((element) => avatar.damageType?.toLowerCase().includes(element.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.rarity.length > 0) {
|
||||
filteredList = filteredList.filter((avatar) => {
|
||||
return newFilter.rarity.some((rarity) => avatar.rank?.toLowerCase().includes(rarity.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
set({ listCopyAvatar: filteredList });
|
||||
},
|
||||
setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => set({ avatarCopySelected: newAvatarSelected }),
|
||||
setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => set({ selectedProfiles: newListAvatar }),
|
||||
setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => {
|
||||
set({
|
||||
listElement: Object.fromEntries(Object.keys(damageType).map(key => [key, false])) as Record<string, boolean>,
|
||||
listPath: Object.fromEntries(Object.keys(baseType).map(key => [key, false])) as Record<string, boolean>,
|
||||
listRank: { "4": false, "5": false }
|
||||
})
|
||||
}
|
||||
}));
|
||||
|
||||
export default useCopyProfileStore;
|
||||
58
src/stores/currentDataStore.ts
Normal file
58
src/stores/currentDataStore.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { AvatarDetail, BaseTypeData, Metadata } from '@/types'
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface CurrentDataState {
|
||||
avatarSelected: AvatarDetail | null;
|
||||
skillIDSelected: string | null;
|
||||
avatarSearch: string;
|
||||
lightconeSearch: string;
|
||||
mapAvatarElementActive: Record<string, boolean>;
|
||||
mapAvatarPathActive: Record<string, boolean>;
|
||||
mapLightconePathActive: Record<string, boolean>;
|
||||
mapLightconeRankActive: Record<string, boolean>;
|
||||
setSettingData: (newMetaData: Metadata) => void;
|
||||
setAvatarSelected: (avatar: AvatarDetail | null) => void;
|
||||
setSkillIDSelected: (skillID: string | null) => void;
|
||||
setAvatarSearch: (avatarSearch: string) => void;
|
||||
setLightconeSearch: (lightconeSearch: string) => void;
|
||||
setMapAvatarElementActive: (mapAvatarElementActive: Record<string, boolean>) => void;
|
||||
setMapAvatarPathActive: (mapAvatarPathActive: Record<string, boolean>) => void;
|
||||
setMapLightconePathActive: (mapLightconePathActive: Record<string, boolean>) => void;
|
||||
setMapLightconeRankActive: (mapLightconeRankActive: Record<string, boolean>) => void;
|
||||
setResetDataLightcone: (avtarSeleted: AvatarDetail| null, baseType: Record<string, BaseTypeData>) => void;
|
||||
}
|
||||
|
||||
const useCurrentDataStore = create<CurrentDataState>((set) => ({
|
||||
avatarSelected: null,
|
||||
skillIDSelected: null,
|
||||
mapAvatarElementActive: {},
|
||||
mapAvatarPathActive: {},
|
||||
mapLightconePathActive: {},
|
||||
mapLightconeRankActive: {"3": false, "4": false, "5": false },
|
||||
avatarSearch: "",
|
||||
lightconeSearch: "",
|
||||
setSettingData: (newMetaData: Metadata) => set({
|
||||
mapAvatarElementActive: Object.fromEntries(Object.keys(newMetaData.DamageType).map(key => [key, false])) as Record<string, boolean>,
|
||||
mapAvatarPathActive: Object.fromEntries(Object.keys(newMetaData.BaseType).map(key => [key, false])) as Record<string, boolean>,
|
||||
mapLightconePathActive: Object.fromEntries(Object.keys(newMetaData.BaseType).map(key => [key, false])) as Record<string, boolean>,
|
||||
}),
|
||||
setAvatarSearch: (avatarSearch: string) => set({ avatarSearch }),
|
||||
setLightconeSearch: (lightconeSearch: string) => set({ lightconeSearch }),
|
||||
setAvatarSelected: (avatar: AvatarDetail | null) => set({ avatarSelected: avatar }),
|
||||
setSkillIDSelected: (skillID: string | null) => set({ skillIDSelected: skillID }),
|
||||
setMapAvatarElementActive: (mapAvatarElementActive: Record<string, boolean>) => set({ mapAvatarElementActive }),
|
||||
setMapAvatarPathActive: (mapAvatarPathActive: Record<string, boolean>) => set({ mapAvatarPathActive }),
|
||||
setMapLightconePathActive: (mapLightconePathActive: Record<string, boolean>) => set({ mapLightconePathActive }),
|
||||
setMapLightconeRankActive: (mapLightconeRankActive: Record<string, boolean>) => set({ mapLightconeRankActive }),
|
||||
setResetDataLightcone: (avtarSeleted: AvatarDetail| null, baseType: Record<string, BaseTypeData>) => {
|
||||
const newListPath: Record<string, boolean> = Object.fromEntries(Object.keys(baseType).map(key => [key, false]))
|
||||
const newListRank: Record<string, boolean> = {"3": false, "4": false, "5": false }
|
||||
if (avtarSeleted) {
|
||||
newListPath[avtarSeleted.BaseType] = true
|
||||
}
|
||||
set({ mapLightconeRankActive: newListRank })
|
||||
set({ mapLightconePathActive: newListPath })
|
||||
}
|
||||
}))
|
||||
|
||||
export default useCurrentDataStore;
|
||||
69
src/stores/detailDataStore.ts
Normal file
69
src/stores/detailDataStore.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ASGroupDetail, AvatarDetail, BaseTypeData, DamageTypeData, EliteData, HardLevelData, LightConeDetail, MainAffixData, Metadata, MOCGroupDetail, MonsterDetail, PeakGroupDetail, PFGroupDetail, RelicSetDetail, SkillMaxLevelData, StageData, SubAffixData } from '@/types'
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface DetailDataState {
|
||||
mapMonster: Record<string, MonsterDetail>
|
||||
mapRelicSet: Record<string, RelicSetDetail>
|
||||
mapAvatar: Record<string, AvatarDetail>
|
||||
mapLightCone: Record<string, LightConeDetail>
|
||||
mapAS: Record<string, ASGroupDetail>
|
||||
mapMoc: Record<string, MOCGroupDetail>
|
||||
mapPF: Record<string, PFGroupDetail>
|
||||
mapPeak: Record<string, PeakGroupDetail>
|
||||
baseType: Record<string, BaseTypeData>;
|
||||
damageType: Record<string, DamageTypeData>;
|
||||
mainAffix: Record<string, Record<string, MainAffixData>>;
|
||||
subAffix: Record<string, Record<string, SubAffixData>>;
|
||||
skillConfig: Record<string, SkillMaxLevelData>;
|
||||
stage: Record<string, StageData>;
|
||||
hardLevelConfig: Record<string, Record<string, HardLevelData>>;
|
||||
eliteConfig: Record<string, EliteData>;
|
||||
setMetaData: (newMetaData: Metadata) => void;
|
||||
setMapMonster: (newMonster: Record<string, MonsterDetail>) => void
|
||||
setMapRelicSet: (newRelicSet: Record<string, RelicSetDetail>) => void
|
||||
setMapAvatar: (newAvatar: Record<string, AvatarDetail>) => void
|
||||
setMapLightCone: (newLightCone: Record<string, LightConeDetail>) => void
|
||||
setMapAS: (newAS: Record<string, ASGroupDetail>) => void
|
||||
setMapMoc: (newMoc: Record<string, MOCGroupDetail>) => void
|
||||
setMapPF: (newPF: Record<string, PFGroupDetail>) => void
|
||||
setMapPeak: (newPeak: Record<string, PeakGroupDetail>) => void
|
||||
}
|
||||
|
||||
const useDetailDataStore = create<DetailDataState>((set) => ({
|
||||
mapMonster: {},
|
||||
mapRelicSet: {},
|
||||
mapAvatar: {},
|
||||
mapLightCone: {},
|
||||
mapAS: {},
|
||||
mapMoc: {},
|
||||
mapPF: {},
|
||||
mapPeak: {},
|
||||
baseType: {},
|
||||
damageType: {},
|
||||
mainAffix: {},
|
||||
subAffix: {},
|
||||
skillConfig: {},
|
||||
stage: {},
|
||||
hardLevelConfig: {},
|
||||
eliteConfig: {},
|
||||
setMapMonster: (newMonster) => set({ mapMonster: newMonster }),
|
||||
setMapRelicSet: (newRelicSet) => set({ mapRelicSet: newRelicSet }),
|
||||
setMapAvatar: (newAvatar) => set({ mapAvatar: newAvatar }),
|
||||
setMapLightCone: (newLightCone) => set({ mapLightCone: newLightCone }),
|
||||
setMapAS: (newAS) => set({ mapAS: newAS }),
|
||||
setMapMoc: (newMoc) => set({ mapMoc: newMoc }),
|
||||
setMapPF: (newPF) => set({ mapPF: newPF }),
|
||||
setMapPeak: (newPeak) => set({ mapPeak: newPeak }),
|
||||
setMetaData: (newMetaData: Metadata) => set({
|
||||
baseType: newMetaData.BaseType,
|
||||
damageType: newMetaData.DamageType,
|
||||
mainAffix: newMetaData.MainAffix,
|
||||
subAffix: newMetaData.SubAffix,
|
||||
skillConfig: newMetaData.SkillConfig,
|
||||
stage: newMetaData.Stage,
|
||||
hardLevelConfig: newMetaData.HardLevelConfig,
|
||||
eliteConfig: newMetaData.EliteConfig,
|
||||
}),
|
||||
}))
|
||||
|
||||
export default useDetailDataStore
|
||||
@@ -1,43 +0,0 @@
|
||||
import { MocDetail, EventBasic, PFDetail, ASDetail, PeakDetail } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
|
||||
interface EventState {
|
||||
MOCEvent: EventBasic[];
|
||||
mapMOCInfo: Record<string, MocDetail[]>;
|
||||
PFEvent: EventBasic[];
|
||||
mapPFInfo: Record<string, PFDetail>;
|
||||
ASEvent: EventBasic[];
|
||||
mapASInfo: Record<string, ASDetail>;
|
||||
PEAKEvent: EventBasic[];
|
||||
mapPEAKInfo: Record<string, PeakDetail>;
|
||||
setMOCEvent: (newListEvent: EventBasic[]) => void;
|
||||
setMapMOCInfo: (newMapMOCInfo: Record<string, MocDetail[]>) => void;
|
||||
setPFEvent: (newListEvent: EventBasic[]) => void;
|
||||
setMapPFInfo: (newMapPFInfo: Record<string, PFDetail>) => void;
|
||||
setASEvent: (newListEvent: EventBasic[]) => void;
|
||||
setMapASInfo: (newMapASInfo: Record<string, ASDetail>) => void;
|
||||
setPEAKEvent: (newListEvent: EventBasic[]) => void;
|
||||
setMapPEAKInfo: (newMapPEAKInfo: Record<string, PeakDetail>) => void;
|
||||
}
|
||||
|
||||
const useEventStore = create<EventState>((set) => ({
|
||||
MOCEvent: [],
|
||||
mapMOCInfo: {},
|
||||
PFEvent: [],
|
||||
mapPFInfo: {},
|
||||
ASEvent: [],
|
||||
mapASInfo: {},
|
||||
PEAKEvent: [],
|
||||
mapPEAKInfo: {},
|
||||
setMOCEvent: (newListEvent: EventBasic[]) => set({ MOCEvent: newListEvent }),
|
||||
setMapMOCInfo: (newMapMOCInfo: Record<string, MocDetail[]>) => set({ mapMOCInfo: newMapMOCInfo }),
|
||||
setPFEvent: (newListEvent: EventBasic[]) => set({ PFEvent: newListEvent }),
|
||||
setMapPFInfo: (newMapPFInfo: Record<string, PFDetail>) => set({ mapPFInfo: newMapPFInfo }),
|
||||
setASEvent: (newListEvent: EventBasic[]) => set({ ASEvent: newListEvent }),
|
||||
setMapASInfo: (newMapASInfo: Record<string, ASDetail>) => set({ mapASInfo: newMapASInfo }),
|
||||
setPEAKEvent: (newListEvent: EventBasic[]) => set({ PEAKEvent: newListEvent }),
|
||||
setMapPEAKInfo: (newMapPEAKInfo: Record<string, PeakDetail>) => set({ mapPEAKInfo: newMapPEAKInfo }),
|
||||
}));
|
||||
|
||||
export default useEventStore;
|
||||
@@ -1,67 +0,0 @@
|
||||
import { LightConeBasic, FilterLightconeType, LightConeDetail } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface LightconeState {
|
||||
listLightcone: LightConeBasic[];
|
||||
listRawLightcone: LightConeBasic[];
|
||||
listPath: Record<string, boolean>;
|
||||
listRank: Record<string, boolean>;
|
||||
filter: FilterLightconeType;
|
||||
defaultFilter: { path: string[], rarity: string[] };
|
||||
mapLightconeInfo: Record<string, LightConeDetail>;
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
setListRank: (newListRank: Record<string, boolean>) => void;
|
||||
setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => void;
|
||||
setListLightcone: (newListLightcone: LightConeBasic[]) => void;
|
||||
setFilter: (newFilter: FilterLightconeType) => void;
|
||||
setMapLightconeInfo: (lightconeId: string, newLightcone: LightConeDetail) => void;
|
||||
setAllMapLightconeInfo: (newLightcone: Record<string, LightConeDetail>) => void;
|
||||
}
|
||||
|
||||
const useLightconeStore = create<LightconeState>((set, get) => ({
|
||||
listLightcone: [],
|
||||
listRawLightcone: [],
|
||||
mapLightconeInfo: {},
|
||||
|
||||
filter: {
|
||||
name: "",
|
||||
path: [],
|
||||
locale: "",
|
||||
rarity: [],
|
||||
},
|
||||
defaultFilter: { path: [], rarity: [] },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, elation: false },
|
||||
listRank: { "3": false, "4": false, "5": false },
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
|
||||
setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => set({ defaultFilter: newDefaultFilter }),
|
||||
setListLightcone: (newListLightcone: LightConeBasic[]) => set({ listLightcone: newListLightcone, listRawLightcone: newListLightcone }),
|
||||
setFilter: (newFilter: FilterLightconeType) => {
|
||||
set({ filter: newFilter })
|
||||
|
||||
if (newFilter.locale === "") {
|
||||
return
|
||||
}
|
||||
let filteredList = get().listRawLightcone;
|
||||
if (newFilter.name && newFilter.locale) {
|
||||
filteredList = filteredList.filter((lightcone) => {
|
||||
return lightcone.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.path && newFilter.path.length > 0) {
|
||||
filteredList = filteredList.filter((lightcone) => {
|
||||
return newFilter.path.some((path) => lightcone.baseType?.toLowerCase().includes(path.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
if (newFilter.rarity && newFilter.rarity.length > 0) {
|
||||
filteredList = filteredList.filter((lightcone) => {
|
||||
return newFilter.rarity.some((rarity) => lightcone.rank?.toLowerCase().includes(rarity.toLowerCase())) ?? false;
|
||||
});
|
||||
}
|
||||
set({ listLightcone: filteredList });
|
||||
},
|
||||
setMapLightconeInfo: (lightconeId: string, newLightcone: LightConeDetail) => set((state) => ({ mapLightconeInfo: { ...state.mapLightconeInfo, [lightconeId]: newLightcone } })),
|
||||
setAllMapLightconeInfo: (newLightcone: Record<string, LightConeDetail>) => set({ mapLightconeInfo: newLightcone }),
|
||||
}));
|
||||
|
||||
export default useLightconeStore;
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ASConfigMaze, AvatarConfigMaze, ConfigMaze, MOCConfigMaze, PFConfigMaze, SkillConfigMaze, StageConfigMaze } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface MazeState {
|
||||
Technique: Record<string, AvatarConfigMaze>;
|
||||
MOC: Record<string, MOCConfigMaze>;
|
||||
AS: Record<string, ASConfigMaze>;
|
||||
PF: Record<string, PFConfigMaze>;
|
||||
Stage: Record<string, StageConfigMaze>;
|
||||
SkillTree: Record<string, SkillConfigMaze>;
|
||||
setTechnique: (newTechnique: Record<string, AvatarConfigMaze>) => void;
|
||||
setMOC: (newMOC: Record<string, MOCConfigMaze>) => void;
|
||||
setAS: (newAS: Record<string, ASConfigMaze>) => void;
|
||||
setPF: (newPF: Record<string, PFConfigMaze>) => void;
|
||||
setStage: (newStage: Record<string, StageConfigMaze>) => void;
|
||||
setSkillTree: (newSkillTree: Record<string, SkillConfigMaze>) => void;
|
||||
setAllMazeData: (newData: ConfigMaze) => void;
|
||||
}
|
||||
|
||||
const useMazeStore = create<MazeState>((set) => ({
|
||||
Technique: {},
|
||||
MOC: {},
|
||||
AS: {},
|
||||
PF: {},
|
||||
Stage: {},
|
||||
SkillTree: {},
|
||||
setSkillTree: (newSkillTree: Record<string, SkillConfigMaze>) => set({ SkillTree: newSkillTree }),
|
||||
setTechnique: (newTechnique: Record<string, AvatarConfigMaze>) => set({ Technique: newTechnique }),
|
||||
setMOC: (newMOC: Record<string, MOCConfigMaze>) => set({ MOC: newMOC }),
|
||||
setAS: (newAS: Record<string, ASConfigMaze>) => set({ AS: newAS }),
|
||||
setPF: (newPF: Record<string, PFConfigMaze>) => set({ PF: newPF }),
|
||||
setStage: (newStage: Record<string, StageConfigMaze>) => set({ Stage: newStage }),
|
||||
setAllMazeData: (newData: ConfigMaze) => set({
|
||||
Technique: newData.Avatar,
|
||||
MOC: newData.MOC,
|
||||
AS: newData.AS,
|
||||
PF: newData.PF,
|
||||
Stage: newData.Stage,
|
||||
SkillTree: newData.Skill
|
||||
}),
|
||||
}));
|
||||
|
||||
export default useMazeStore;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MonsterBasic } from '@/types'
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface MonsterState {
|
||||
listMonster: MonsterBasic[]
|
||||
mapMonster: Record<string, MonsterBasic>
|
||||
setListMonster: (newListMonster: MonsterBasic[]) => void
|
||||
setAllMapMonster: (newMonster: Record<string, MonsterBasic>) => void
|
||||
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => void
|
||||
}
|
||||
|
||||
const useMonsterStore = create<MonsterState>((set) => ({
|
||||
listMonster: [],
|
||||
mapMonster: {},
|
||||
|
||||
setListMonster: (newListMonster) =>
|
||||
set({ listMonster: newListMonster }),
|
||||
|
||||
setMapMonster: (monsterId, newMonster) =>
|
||||
set((state) => ({
|
||||
mapMonster: { ...state.mapMonster, [monsterId]: newMonster },
|
||||
})),
|
||||
|
||||
setAllMapMonster: (newMonster) =>
|
||||
set({ mapMonster: newMonster }),
|
||||
}))
|
||||
|
||||
export default useMonsterStore
|
||||
@@ -1,16 +0,0 @@
|
||||
import { RelicDetail } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface RelicState {
|
||||
mapRelicInfo: Record<string, RelicDetail>;
|
||||
setMapRelicInfo: (lightconeId: string, newRelic: RelicDetail) => void;
|
||||
setAllMapRelicInfo: (newRelic: Record<string, RelicDetail>) => void;
|
||||
}
|
||||
|
||||
const useRelicStore = create<RelicState>((set, get) => ({
|
||||
mapRelicInfo: {},
|
||||
setMapRelicInfo: (lightconeId: string, newRelic: RelicDetail) => set((state) => ({ mapRelicInfo: { ...state.mapRelicInfo, [lightconeId]: newRelic } })),
|
||||
setAllMapRelicInfo: (newRelic: Record<string, RelicDetail>) => set({ mapRelicInfo: newRelic }),
|
||||
}));
|
||||
|
||||
export default useRelicStore;
|
||||
@@ -1,6 +0,0 @@
|
||||
export type AffixDetail = {
|
||||
property: string;
|
||||
base: number;
|
||||
step: number;
|
||||
step_num: number;
|
||||
}
|
||||
@@ -1,70 +1,124 @@
|
||||
import { ChallengeDetail, EventStageDetail } from "./mocDetail"
|
||||
import { BuffDetail, OptionDetail } from "./pfDetail"
|
||||
import { ExtraEffect } from "./avatarDetail";
|
||||
import { InfiniteWave, MazeBuff } from "./pfDetail";
|
||||
|
||||
export interface ASDetail {
|
||||
Id: number
|
||||
Name: string
|
||||
Buff?: BuffDetail
|
||||
BuffList1: OptionDetail[]
|
||||
BuffList2: OptionDetail[]
|
||||
BeginTime: string
|
||||
EndTime: string
|
||||
Level: ASLevel[]
|
||||
export interface ASGroupDetail {
|
||||
ID: number;
|
||||
ChallengeGroupType: string;
|
||||
Name: Record<string, string>;
|
||||
Image: ASGroupImage;
|
||||
BeginTime: string;
|
||||
EndTime: string;
|
||||
BuffList1: ASBuff[];
|
||||
BuffList2: ASBuff[];
|
||||
Level: ASLevel[];
|
||||
}
|
||||
|
||||
export interface ASGroupImage {
|
||||
BackGroundPath: string;
|
||||
TabPicPath: string;
|
||||
TabPicSelectPath: string;
|
||||
ThemePicPath: string;
|
||||
}
|
||||
|
||||
export interface ASBuff {
|
||||
ID: number;
|
||||
Param: number[];
|
||||
Icon: string;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
ExtraList?: ExtraEffect[];
|
||||
}
|
||||
|
||||
export interface ASLevel {
|
||||
Id: number
|
||||
Name: string
|
||||
Challenge: ChallengeDetail[]
|
||||
DamageType1: string[]
|
||||
DamageType2: string[]
|
||||
MazeGroupID1: number
|
||||
MazeGroupID2: number
|
||||
BossMonsterID1: number
|
||||
BossMonsterID2: number
|
||||
BossMonsterID1SkillList: number[]
|
||||
BossMonsterID2SkillList: number[]
|
||||
BossMonsterConfig1: BossMonsterConfig
|
||||
BossMonsterConfig2: BossMonsterConfig
|
||||
EventIDList1: EventStageDetail[]
|
||||
EventIDList2: EventStageDetail[]
|
||||
Floor: number;
|
||||
ID: number;
|
||||
StageNum: number;
|
||||
Name: Record<string, string>;
|
||||
Target: ASTarget[];
|
||||
DamageType1: string[];
|
||||
DamageType2: string[];
|
||||
MazeBuff: MazeBuff[];
|
||||
TurnLimit: number;
|
||||
EventList1: ASEvent[];
|
||||
EventList2: ASEvent[];
|
||||
Monster1: ASMonster;
|
||||
Monster2: ASMonster;
|
||||
}
|
||||
|
||||
export interface BossMonsterConfig {
|
||||
Difficulty: number
|
||||
DifficultyList: number[]
|
||||
TagList: BossTag[]
|
||||
DifficultyGuideList: BossDifficultyGuide[]
|
||||
TextGuideList: string[]
|
||||
PhaseList: BossPhase[]
|
||||
export interface ASTarget {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Param: number[];
|
||||
}
|
||||
|
||||
export interface BossTag {
|
||||
Name: string
|
||||
Desc: string
|
||||
Param: number[]
|
||||
SkillID?: number | null
|
||||
ParamFix: number[]
|
||||
Child: BossChildTag[]
|
||||
export interface ASEvent {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
HardLevelGroup: number;
|
||||
EliteGroup: number;
|
||||
Level: number;
|
||||
Release: boolean;
|
||||
MonsterList: number[][];
|
||||
Infinite: InfiniteWave[] | null;
|
||||
}
|
||||
|
||||
export interface BossChildTag {
|
||||
Name: string
|
||||
Desc: string
|
||||
Param: number[]
|
||||
export interface ASMonster {
|
||||
ID: number;
|
||||
Tag: ASTag[];
|
||||
Phase: ASPhase[];
|
||||
DifficultyGuide: ASDifficultyGuide[];
|
||||
TextGuide: ASTextGuide[];
|
||||
}
|
||||
|
||||
export interface BossDifficultyGuide {
|
||||
Desc: string
|
||||
Param: number[]
|
||||
SkillID?: number | null
|
||||
ParamFix: number[]
|
||||
export interface ASTag {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
BriefDescription: Record<string, string>;
|
||||
Param: number[];
|
||||
SkillID: number | null;
|
||||
Effect: ASEffect[];
|
||||
}
|
||||
|
||||
export interface BossPhase {
|
||||
Name: string
|
||||
Desc: string
|
||||
Answer: string
|
||||
Difficulty: number
|
||||
SkillList: number[]
|
||||
export interface ASEffect {
|
||||
ID: number;
|
||||
Desc: Record<string, string>;
|
||||
Name: Record<string, string>;
|
||||
Icon: string;
|
||||
Param: number[];
|
||||
EffectType: number;
|
||||
}
|
||||
|
||||
export interface ASPhase {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Answer: Record<string, string>;
|
||||
Description: Record<string, string>;
|
||||
SkillList: ASSkillList[];
|
||||
}
|
||||
|
||||
export interface ASSkillList {
|
||||
ID: number;
|
||||
SkillType: string;
|
||||
Name: Record<string, string>;
|
||||
TextList: ASTextList[];
|
||||
}
|
||||
|
||||
export interface ASTextList {
|
||||
ID: number;
|
||||
Description: Record<string, string>;
|
||||
Param: number[];
|
||||
Effect: ASEffect[];
|
||||
}
|
||||
|
||||
export interface ASDifficultyGuide {
|
||||
ID: number;
|
||||
Description: Record<string, string>;
|
||||
Param: number[];
|
||||
SkillID: number | null;
|
||||
}
|
||||
|
||||
export interface ASTextGuide {
|
||||
ID: number;
|
||||
Description: Record<string, string>;
|
||||
Param: number[];
|
||||
}
|
||||
225
src/types/avatarDetail.ts
Normal file
225
src/types/avatarDetail.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
export interface AvatarDetail {
|
||||
ID: number;
|
||||
BaseType: string;
|
||||
DamageType: string;
|
||||
Name: Record<string, string>;
|
||||
Image: AvatarImage;
|
||||
Release: boolean;
|
||||
MaxPromotion: number;
|
||||
MaxRank: number;
|
||||
SPNeed: number | null;
|
||||
Rarity: string;
|
||||
BGDesc: Record<string, string>;
|
||||
Lightcones: number[];
|
||||
Stats: Record<string, StatLevel>;
|
||||
Skins: Record<string, AvatarSkin>;
|
||||
Ranks: Record<string, RankDetail>;
|
||||
Skills: Record<string, SkillDetail>;
|
||||
Memosprite: Memosprite | null;
|
||||
SkillTrees: Record<string, Record<string, SkillTreePoint>>;
|
||||
Relics: Relics;
|
||||
Teams: AvatarTeams;
|
||||
Enhanced: Record<string, EnhancedData> | null;
|
||||
Unique: Record<string, GlobalBuff> | null;
|
||||
MazeBuff: number[];
|
||||
}
|
||||
|
||||
export interface AvatarImage {
|
||||
DefaultAvatarHeadIconPath: string;
|
||||
AvatarSideIconPath: string;
|
||||
AvatarMiniIconPath: string;
|
||||
AvatarGachaResultImgPath: string;
|
||||
ActionAvatarHeadIconPath: string;
|
||||
SideAvatarHeadIconPath: string;
|
||||
WaitingAvatarHeadIconPath: string;
|
||||
AvatarCutinImgPath: string;
|
||||
AvatarCutinBgImgPath: string;
|
||||
AvatarCutinFrontImgPath: string;
|
||||
AvatarIconPath: string;
|
||||
}
|
||||
|
||||
|
||||
export interface StatLevel {
|
||||
Promotion: number;
|
||||
MaxLevel: number;
|
||||
WorldLevelRequirement: number;
|
||||
PlayerLevelRequirement: number;
|
||||
AttackBase: number;
|
||||
AttackAdd: number;
|
||||
DefenceBase: number;
|
||||
DefenceAdd: number;
|
||||
HPBase: number;
|
||||
HPAdd: number;
|
||||
SpeedBase: number;
|
||||
CriticalChance: number;
|
||||
CriticalDamage: number;
|
||||
BaseAggro: number;
|
||||
}
|
||||
|
||||
export interface AvatarSkin {
|
||||
ID: number;
|
||||
Type: string;
|
||||
PlayerCardID: number;
|
||||
Name: Record<string, string>;
|
||||
PlayerCardTitleText: Record<string, string>;
|
||||
AvatarNameOnDropSkin: Record<string, string>;
|
||||
AvatarSkinSynopsis: Record<string, string>;
|
||||
Image: AvatarSkinImage;
|
||||
}
|
||||
|
||||
export interface AvatarSkinImage {
|
||||
AvatarCutinFrontImgPath: string;
|
||||
DefaultAvatarHeadIconPath: string;
|
||||
AdventureDefaultAvatarHeadIconPath: string;
|
||||
WaitingAvatarHeadIconPath: string;
|
||||
ActionAvatarHeadIconPath: string;
|
||||
SideAvatarHeadIconPath: string;
|
||||
AvatarSideIconPath: string;
|
||||
AvatarCutinImgPath: string;
|
||||
AvatarCutinBgImgPath: string;
|
||||
AvatarMiniIconPath: string;
|
||||
DressIconPath: string;
|
||||
}
|
||||
|
||||
export interface RankDetail {
|
||||
Rank: number;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
RankAbility: string[];
|
||||
SkillAddLevelList: Record<string, number>;
|
||||
Icon: string;
|
||||
Image: string;
|
||||
Param: number[];
|
||||
Extra: Record<string, ExtraEffect>;
|
||||
}
|
||||
|
||||
export interface ExtraEffect {
|
||||
ID: number;
|
||||
Desc: Record<string, string>;
|
||||
Name: Record<string, string>;
|
||||
Icon: string;
|
||||
Param: number[];
|
||||
EffectType: number;
|
||||
}
|
||||
|
||||
export interface SkillDetail {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Tag: Record<string, string>;
|
||||
TypeDesc: Record<string, string>;
|
||||
MaxLevel: number;
|
||||
Level: Record<string, SkillLevel>;
|
||||
Icon: string;
|
||||
Desc: Record<string, string>;
|
||||
RatedSkillTreeID: number[];
|
||||
RatedRankID: number[];
|
||||
Extra: Record<string, ExtraEffect>;
|
||||
SPBase: number | null;
|
||||
StanceDamageDisplay: number;
|
||||
SPMultipleRatio: number | null;
|
||||
BPNeed: number | null;
|
||||
BPAdd: number | null;
|
||||
StanceDamageType: string | null;
|
||||
AttackType: string | null;
|
||||
SkillEffect: string;
|
||||
}
|
||||
|
||||
export interface SkillLevel {
|
||||
Level: number;
|
||||
Param: number[];
|
||||
}
|
||||
|
||||
export interface Memosprite {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Image: MemospriteImage;
|
||||
Skills: Record<string, SkillDetail>;
|
||||
HPBase?: number | null;
|
||||
HPInherit?: number | null;
|
||||
HPSkill?: number | null;
|
||||
SpeedBase?: number | null;
|
||||
SpeedInherit?: number | null;
|
||||
SpeedSkill?: number | null;
|
||||
Aggro?: number | null;
|
||||
}
|
||||
|
||||
export interface MemospriteImage {
|
||||
HeadIcon: string;
|
||||
UnCreateHeadIconPath: string;
|
||||
WaitingServantHeadIconPath: string;
|
||||
ActionServantHeadIconPath: string;
|
||||
ServantSideIconPath: string;
|
||||
ServantMiniIconPath: string;
|
||||
}
|
||||
|
||||
export interface SkillTreePoint {
|
||||
PointID: number;
|
||||
PointType: number;
|
||||
AnchorType: string;
|
||||
Level: number;
|
||||
MaxLevel: number;
|
||||
PrePoint: number[];
|
||||
StatusAddList: StatusAdd[];
|
||||
LevelUpSkillID: number[];
|
||||
Icon: string;
|
||||
PointName: Record<string, string>;
|
||||
PointDesc: Record<string, string>;
|
||||
Extra: Record<string, ExtraEffect>;
|
||||
Param: number[];
|
||||
}
|
||||
|
||||
|
||||
export interface StatusAdd {
|
||||
PropertyType: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
export interface Relics {
|
||||
Set4IDList: number[];
|
||||
Set2IDList: number[];
|
||||
PropertyList3: string[];
|
||||
PropertyList4: string[];
|
||||
PropertyList5: string[];
|
||||
PropertyList6: string[];
|
||||
PropertyList: RelicProperty[];
|
||||
SubAffixPropertyList: string[];
|
||||
ScoreRankList: number[];
|
||||
LocalCriticalChance: { Value: number };
|
||||
}
|
||||
|
||||
export interface RelicProperty {
|
||||
RelicType: string;
|
||||
PropertyType: string;
|
||||
}
|
||||
|
||||
export interface AvatarTeams {
|
||||
TeamID: number;
|
||||
Position: number;
|
||||
MemberList: number[];
|
||||
BackupList1: number[];
|
||||
BackupList2: number[];
|
||||
BackupList3: number[];
|
||||
BackupGroupList1: number[];
|
||||
BackupGroupList2: number[];
|
||||
BackupGroupList3: number[];
|
||||
}
|
||||
|
||||
export interface EnhancedData {
|
||||
EnhancedID: number;
|
||||
SPNeed: number | null;
|
||||
Ranks: Record<string, RankDetail>;
|
||||
Skills: Record<string, SkillDetail>;
|
||||
SkillTrees: Record<string, Record<string, SkillTreePoint>> | null;
|
||||
}
|
||||
|
||||
export interface GlobalBuff {
|
||||
ID: number;
|
||||
AvatarID: number;
|
||||
Name: Record<string, string>;
|
||||
Tag: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
Param: number[];
|
||||
Extra: Record<string, ExtraEffect>;
|
||||
MazeBuffID: number | null;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface CharacterBasic {
|
||||
id: string;
|
||||
icon: string;
|
||||
rank: string;
|
||||
baseType: string;
|
||||
damageType: string;
|
||||
lang: Record<string, string>;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
|
||||
export interface CharacterDetail {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
CharaInfo: CharacterInfo;
|
||||
Rarity: string;
|
||||
AvatarVOTag: string;
|
||||
SPNeed: number | null;
|
||||
BaseType: string;
|
||||
DamageType: string;
|
||||
Ranks: Record<string, RankType>;
|
||||
Skills: Record<string, SkillType>;
|
||||
SkillTrees: Record<string, Record<string, SkillTreePoint>>;
|
||||
Memosprite: Memosprite;
|
||||
Unique: Record<string, UniqueAbility>;
|
||||
Stats: Record<string, Stat>;
|
||||
Relics: Relics;
|
||||
Enhanced: Record<string, EnhancedType>;
|
||||
RankIcon: string[];
|
||||
}
|
||||
|
||||
export interface EnhancedType {
|
||||
Descs: string[];
|
||||
ChangeRankList: unknown;
|
||||
ChangeSkillTreeList: unknown;
|
||||
Ranks: Record<string, RankType>;
|
||||
Skills: Record<string, SkillType>;
|
||||
SkillTrees: Record<string, Record<string, SkillTreePoint>>;
|
||||
}
|
||||
|
||||
export interface CharacterInfo {
|
||||
Camp: string | null;
|
||||
VA: VoiceActors;
|
||||
Stories: Record<string, string | null>;
|
||||
Voicelines: string[];
|
||||
}
|
||||
|
||||
export interface VoiceActors {
|
||||
Chinese: string | null;
|
||||
Japanese: string | null;
|
||||
Korean: string | null;
|
||||
English: string | null;
|
||||
}
|
||||
|
||||
export interface RankType {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Icon: string;
|
||||
ParamList: number[];
|
||||
}
|
||||
|
||||
export interface SkillType {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Desc: string | null;
|
||||
SimpleDesc: string;
|
||||
Type: string;
|
||||
Tag: string;
|
||||
SPBase: number | null;
|
||||
BPNeed: number;
|
||||
BPAdd: number;
|
||||
ShowStanceList: number[];
|
||||
SkillComboValueDelta: number | null;
|
||||
Level: Record<string, LevelParams>;
|
||||
}
|
||||
|
||||
export interface LevelParams {
|
||||
Level: number;
|
||||
ParamList: number[];
|
||||
}
|
||||
|
||||
export type StatusAddType = {
|
||||
$type: string;
|
||||
PropertyType: string;
|
||||
Value: number;
|
||||
Name: string;
|
||||
};
|
||||
|
||||
export interface SkillTreePoint {
|
||||
Anchor: string;
|
||||
AvatarPromotionLimit: number | null;
|
||||
AvatarLevelLimit: number | null;
|
||||
DefaultUnlock: boolean;
|
||||
Icon: string;
|
||||
LevelUpSkillID: number[];
|
||||
MaterialList: ItemConfigRow[];
|
||||
MaxLevel: number;
|
||||
ParamList: number[];
|
||||
PointID: number;
|
||||
PointName: string | null;
|
||||
PointDesc: string | null;
|
||||
PointTriggerKey: number;
|
||||
PointType: number;
|
||||
PrePoint: string[];
|
||||
StatusAddList: StatusAddType[];
|
||||
}
|
||||
|
||||
export interface ItemConfigRow {
|
||||
$type: string;
|
||||
ItemID: number;
|
||||
ItemNum: number;
|
||||
Rarity: string;
|
||||
}
|
||||
|
||||
export interface Memosprite {
|
||||
Name: string;
|
||||
Icon: string;
|
||||
HPBase: string;
|
||||
HPInherit: string;
|
||||
HPSkill: number | null;
|
||||
SpeedBase: string;
|
||||
SpeedInherit: string;
|
||||
SpeedSkill: number;
|
||||
Aggro: number;
|
||||
Skills: Record<string, SpriteSkill>;
|
||||
Talent: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SpriteSkill {
|
||||
Name: string;
|
||||
Desc: string | null;
|
||||
SimpleDesc: string;
|
||||
Type: string | null;
|
||||
Tag: string;
|
||||
SPBase: number | null;
|
||||
BPNeed: number;
|
||||
BPAdd: number | null;
|
||||
ShowStanceList: number[];
|
||||
SkillComboValueDelta: number | null;
|
||||
Extra: Record<string, Extra>;
|
||||
Level: Record<string, LevelParams>;
|
||||
}
|
||||
|
||||
export interface UniqueAbility {
|
||||
Tag: string;
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Param: number[];
|
||||
Extra: Record<string, Extra>;
|
||||
}
|
||||
|
||||
export interface Extra {
|
||||
name: string;
|
||||
desc: string;
|
||||
param: number[];
|
||||
}
|
||||
|
||||
export interface Stat {
|
||||
AttackBase: number;
|
||||
AttackAdd: number;
|
||||
DefenceBase: number;
|
||||
DefenceAdd: number;
|
||||
HPBase: number;
|
||||
HPAdd: number;
|
||||
SpeedBase: number;
|
||||
CriticalChance: number;
|
||||
CriticalDamage: number;
|
||||
BaseAggro: number;
|
||||
Cost: ItemConfigRow[];
|
||||
}
|
||||
|
||||
export interface Relics {
|
||||
AvatarID: number;
|
||||
Set4IDList: number[];
|
||||
Set2IDList: number[];
|
||||
PropertyList3: string[];
|
||||
PropertyList4: string[];
|
||||
PropertyList5: string[];
|
||||
PropertyList6: string[];
|
||||
PropertyList: RelicRecommendProperty[];
|
||||
SubAffixPropertyList: string[];
|
||||
ScoreRankList: number[];
|
||||
}
|
||||
|
||||
export interface RelicRecommendProperty {
|
||||
$type: string;
|
||||
RelicType: string;
|
||||
PropertyType: string;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
export type ASConfigMaze = {
|
||||
buff_1: number[];
|
||||
buff_2: number[];
|
||||
maze_buff: number;
|
||||
}
|
||||
|
||||
export type PFConfigMaze = {
|
||||
buff: number[];
|
||||
maze_buff: number;
|
||||
}
|
||||
|
||||
export type MOCConfigMaze = {
|
||||
maze_buff: number;
|
||||
}
|
||||
|
||||
export type AvatarConfigMaze = {
|
||||
maze_buff: number[];
|
||||
}
|
||||
|
||||
export type StageConfigMaze = {
|
||||
stage_id: number;
|
||||
stage_type: string;
|
||||
level: number;
|
||||
monster_list: Array<Record<string, number>>;
|
||||
};
|
||||
|
||||
export type SkillConfigMaze = {
|
||||
max_level: number;
|
||||
index_slot: number;
|
||||
}
|
||||
|
||||
export type ConfigMaze = {
|
||||
Avatar: Record<string, AvatarConfigMaze>;
|
||||
MOC: Record<string, MOCConfigMaze>;
|
||||
AS: Record<string, ASConfigMaze>;
|
||||
PF: Record<string, PFConfigMaze>;
|
||||
Stage: Record<string, StageConfigMaze>;
|
||||
Skill: Record<string, SkillConfigMaze>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
export interface EventBasic {
|
||||
id: string;
|
||||
begin: string;
|
||||
end: string;
|
||||
lang: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
export * from "./characterBasic"
|
||||
export * from "./characterDetail"
|
||||
export * from "./avatarDetail"
|
||||
export * from "./srtools"
|
||||
export * from "./filter"
|
||||
export * from "./mics"
|
||||
export * from "./config_maze"
|
||||
export * from "./lightconeBasic"
|
||||
export * from "./lightconeDetail"
|
||||
export * from "./relicDetail"
|
||||
export * from "./affix"
|
||||
export * from "./enka"
|
||||
export * from "./card"
|
||||
export * from "./eventBasic"
|
||||
export * from "./monsterBasic"
|
||||
export * from "./pfDetail"
|
||||
export * from "./asDetail"
|
||||
export * from "./mocDetail"
|
||||
@@ -20,4 +13,8 @@ export * from "./extraData"
|
||||
export * from "./showcase"
|
||||
export * from "./srtools"
|
||||
export * from "./changelog"
|
||||
export * from "./modelConfig"
|
||||
export * from "./modelConfig"
|
||||
export * from "./metaData"
|
||||
export * from "./monsterDetail"
|
||||
export * from "./filter"
|
||||
export * from "./metaData"
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface LightConeBasic {
|
||||
id: string;
|
||||
rank: string;
|
||||
thumbnail: string
|
||||
image: string,
|
||||
baseType: string;
|
||||
lang: Record<string, string>;
|
||||
}
|
||||
@@ -1,27 +1,47 @@
|
||||
export interface LightConeDetail {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
ID: number;
|
||||
Release: boolean;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
Rarity: string;
|
||||
BaseType: string;
|
||||
Refinements: RefinementDetail;
|
||||
Stats: StatEntryDetail[];
|
||||
Bonus: Record<string, { type: string, value: number }[]>
|
||||
MaxPromotionLevel: number;
|
||||
MaxRank: number;
|
||||
Skills: LightconeSkill;
|
||||
Image: LightconeImage;
|
||||
Stats: Record<string, LightconeStatLevel>;
|
||||
}
|
||||
|
||||
interface RefinementDetail {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Level: Record<string, {
|
||||
ParamList: number[];
|
||||
}>;
|
||||
export interface LightconeSkill {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
AbilityName: string;
|
||||
Level: Record<string, LightconeSkillLevel>;
|
||||
}
|
||||
|
||||
interface StatEntryDetail {
|
||||
EquipmentID: number;
|
||||
Promotion?: number;
|
||||
PromotionCostList: PromotionCost[];
|
||||
PlayerLevelRequire?: number;
|
||||
WorldLevelRequire?: number;
|
||||
export interface LightconeSkillLevel {
|
||||
Level: number;
|
||||
Param: number[];
|
||||
Bonus: LightconeSkillBonus[];
|
||||
}
|
||||
|
||||
export interface LightconeSkillBonus {
|
||||
PropertyType: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
export interface LightconeImage {
|
||||
ThumbnailPath: string;
|
||||
ImagePath: string;
|
||||
IconPath: string;
|
||||
FigureIconPath: string;
|
||||
}
|
||||
|
||||
export interface LightconeStatLevel {
|
||||
Promotion: number;
|
||||
PlayerLevelRequire: number;
|
||||
WorldLevelRequire: number;
|
||||
MaxLevel: number;
|
||||
BaseHP: number;
|
||||
BaseHPAdd: number;
|
||||
@@ -29,11 +49,4 @@ interface StatEntryDetail {
|
||||
BaseAttackAdd: number;
|
||||
BaseDefence: number;
|
||||
BaseDefenceAdd: number;
|
||||
}
|
||||
|
||||
interface PromotionCost {
|
||||
$type: string;
|
||||
ItemID: number;
|
||||
ItemNum: number;
|
||||
Rarity: string;
|
||||
}
|
||||
}
|
||||
59
src/types/metaData.ts
Normal file
59
src/types/metaData.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export interface Metadata {
|
||||
BaseType: Record<string, BaseTypeData>;
|
||||
DamageType: Record<string, DamageTypeData>;
|
||||
MainAffix: Record<string, Record<string, MainAffixData>>;
|
||||
SubAffix: Record<string, Record<string, SubAffixData>>;
|
||||
SkillConfig: Record<string, SkillMaxLevelData>;
|
||||
Stage: Record<string, StageData>;
|
||||
HardLevelConfig: Record<string, Record<string, HardLevelData>>;
|
||||
EliteConfig: Record<string, EliteData>;
|
||||
}
|
||||
|
||||
export interface HardLevelData {
|
||||
AttackRatio: number
|
||||
DefenceRatio: number
|
||||
HPRatio: number
|
||||
SpeedRatio: number
|
||||
StanceRatio: number
|
||||
}
|
||||
|
||||
export interface EliteData {
|
||||
AttackRatio: number
|
||||
DefenceRatio: number
|
||||
HPRatio: number
|
||||
SpeedRatio: number
|
||||
StanceRatio: number
|
||||
}
|
||||
|
||||
export interface StageData {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface BaseTypeData {
|
||||
Text: Record<string, string>;
|
||||
Icon: string;
|
||||
}
|
||||
|
||||
export interface DamageTypeData {
|
||||
Name: Record<string, string>;
|
||||
Icon: string;
|
||||
}
|
||||
|
||||
export interface MainAffixData {
|
||||
Property: string;
|
||||
BaseValue: number;
|
||||
LevelAdd: number;
|
||||
}
|
||||
|
||||
export interface SubAffixData {
|
||||
Property: string;
|
||||
BaseValue: number;
|
||||
StepValue: number;
|
||||
StepNum: number;
|
||||
}
|
||||
|
||||
export interface SkillMaxLevelData {
|
||||
MaxLevel: number;
|
||||
IndexSlot: number;
|
||||
}
|
||||
@@ -1,50 +1,49 @@
|
||||
export interface MocDetail {
|
||||
Id: number
|
||||
Name: string
|
||||
GroupName: string
|
||||
Desc: string
|
||||
Param: number[]
|
||||
Challenge: ChallengeDetail[]
|
||||
Countdown: number
|
||||
DamageType1: string[]
|
||||
DamageType2: string[]
|
||||
MazeGroupID1: number
|
||||
MazeGroupID2: number
|
||||
NpcMonsterIDList1: number[]
|
||||
NpcMonsterIDList2: number[]
|
||||
EventIDList1: EventStageDetail[]
|
||||
EventIDList2: EventStageDetail[]
|
||||
BeginTime: string
|
||||
EndTime: string
|
||||
import { InfiniteWave, MazeBuff } from "./pfDetail";
|
||||
|
||||
export interface MOCGroupDetail {
|
||||
ID: number;
|
||||
ChallengeGroupType: string;
|
||||
Name: Record<string, string>;
|
||||
Image: MoCImage;
|
||||
BeginTime: string;
|
||||
EndTime: string;
|
||||
Level: MoCLevel[];
|
||||
}
|
||||
|
||||
export interface ChallengeDetail {
|
||||
Name: string
|
||||
Param?: number | null
|
||||
export interface MoCImage {
|
||||
BackGroundPath: string;
|
||||
TabPicPath: string;
|
||||
TabPicSelectPath: string;
|
||||
ThemePicPath: string;
|
||||
}
|
||||
|
||||
export interface EventStageDetail {
|
||||
StageID: number
|
||||
StageType: string
|
||||
StageName: number
|
||||
HardLevelGroup: number
|
||||
Level: number
|
||||
EliteGroup?: number
|
||||
LevelGraphPath: string
|
||||
StageAbilityConfig: unknown[]
|
||||
BattleScoringGroup?: number | null
|
||||
SubLevelGraphs: unknown[]
|
||||
StageConfigData: StageConfig[]
|
||||
MonsterList: Record<string, number>[]
|
||||
LevelLoseCondition: string[]
|
||||
LevelWinCondition: string[]
|
||||
Release: boolean
|
||||
ForbidExitBattle: boolean
|
||||
MonsterWarningRatio?: number | null
|
||||
TrialAvatarList: unknown[]
|
||||
}
|
||||
export interface StageConfig {
|
||||
$type: string
|
||||
[key: string]: string
|
||||
export interface MoCLevel {
|
||||
Floor: number;
|
||||
ID: number;
|
||||
StageNum: number;
|
||||
Name: Record<string, string>;
|
||||
Target: MoCTarget[];
|
||||
TurnLimit: number;
|
||||
DamageType1: string[];
|
||||
DamageType2: string[];
|
||||
MazeBuff: MazeBuff[];
|
||||
EventList1: MoCEvent[];
|
||||
EventList2: MoCEvent[];
|
||||
}
|
||||
|
||||
export interface MoCTarget {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Param: number[];
|
||||
}
|
||||
|
||||
export interface MoCEvent {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
HardLevelGroup: number;
|
||||
EliteGroup: number;
|
||||
Level: number;
|
||||
Release: boolean;
|
||||
MonsterList: number[][];
|
||||
Infinite: InfiniteWave[] | null;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
export interface MonsterBasic {
|
||||
id: string;
|
||||
rank: string;
|
||||
icon: string;
|
||||
image: string;
|
||||
weak: string[];
|
||||
desc: Record<string, string>;
|
||||
lang: Record<string, string>;
|
||||
}
|
||||
|
||||
|
||||
45
src/types/monsterDetail.ts
Normal file
45
src/types/monsterDetail.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
export interface MonsterDetail {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
EliteGroup: number;
|
||||
HardLevelGroup: number;
|
||||
StanceWeakList: string[];
|
||||
Modify: MonsterModify;
|
||||
Rank: string;
|
||||
StanceCount: number;
|
||||
StanceType: string;
|
||||
Image: MonsterImage;
|
||||
Base: MonsterBase;
|
||||
}
|
||||
|
||||
|
||||
export interface MonsterModify {
|
||||
AttackModifyRatio: number;
|
||||
DefenceModifyRatio: number;
|
||||
HPModifyRatio: number;
|
||||
SpeedModifyRatio: number;
|
||||
StanceModifyRatio: number;
|
||||
SpeedModifyValue: number;
|
||||
StanceModifyValue: number;
|
||||
}
|
||||
|
||||
export interface MonsterImage {
|
||||
IconPath: string;
|
||||
RoundIconPath: string;
|
||||
ImagePath: string;
|
||||
ManikinImagePath: string;
|
||||
}
|
||||
|
||||
export interface MonsterBase {
|
||||
AttackBase: number;
|
||||
DefenceBase: number;
|
||||
HPBase: number;
|
||||
SpeedBase: number;
|
||||
StanceBase: number;
|
||||
CriticalDamageBase: number;
|
||||
StatusResistanceBase: number;
|
||||
SpeedModifyValue: number;
|
||||
StanceModifyValue: number;
|
||||
InitialDelayRatio: number;
|
||||
}
|
||||
@@ -1,37 +1,49 @@
|
||||
import { EventStageDetail } from "./mocDetail";
|
||||
import { InfiniteWave } from "./pfDetail";
|
||||
import { BattleTarget, InfiniteWave, MazeBuff } from "./pfDetail";
|
||||
|
||||
export interface PeakDetail {
|
||||
Id: number;
|
||||
Name: string;
|
||||
PreLevel: PeakLevel[];
|
||||
BossLevel: PeakLevel;
|
||||
BossConfig: BossConfig;
|
||||
export interface PeakGroupDetail {
|
||||
ID: number;
|
||||
ChallengeGroupType: string;
|
||||
Name: Record<string, string>;
|
||||
Image: PeakGroupImage;
|
||||
PreLevel: PeakMazeConfig[];
|
||||
BossLevel: PeakMazeConfig | null;
|
||||
BossConfig: PeakBossConfig | null;
|
||||
}
|
||||
|
||||
export interface PeakLevel {
|
||||
Id: number;
|
||||
Name: string;
|
||||
export interface PeakGroupImage {
|
||||
ThemePosterTabPicPath: string;
|
||||
ThemeIconPicPath: string;
|
||||
HandBookPanelBannerPath: string;
|
||||
RankIconPathList: string[];
|
||||
}
|
||||
|
||||
export interface PeakMazeConfig {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
BattleTarget: BattleTarget[];
|
||||
DamageType: string[];
|
||||
MazeGroupID: number;
|
||||
NpcMonsterIDList: number[];
|
||||
EventIDList: EventStageDetail[];
|
||||
TagList: ChallengeTag[];
|
||||
InfiniteList: Record<string, InfiniteWave>;
|
||||
MazeBuff: MazeBuff[];
|
||||
TurnLimit: number;
|
||||
EventList: PeakEvent[];
|
||||
}
|
||||
|
||||
export interface BossConfig {
|
||||
HardName: string;
|
||||
BuffList: ChallengeTag[];
|
||||
EventIDList: EventStageDetail[];
|
||||
TagList: ChallengeTag[];
|
||||
InfiniteList: Record<string, InfiniteWave>;
|
||||
}
|
||||
|
||||
export interface ChallengeTag {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Param: number[];
|
||||
export interface PeakBossConfig {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
BuffList: MazeBuff[];
|
||||
BattleTarget: BattleTarget[];
|
||||
MazeBuff: MazeBuff[];
|
||||
TurnLimit: number;
|
||||
EventList: PeakEvent[];
|
||||
}
|
||||
|
||||
export interface PeakEvent {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
HardLevelGroup: number;
|
||||
EliteGroup: number;
|
||||
Level: number;
|
||||
Release: boolean;
|
||||
MonsterList: number[][];
|
||||
Infinite: InfiniteWave[] | null;
|
||||
}
|
||||
@@ -1,51 +1,72 @@
|
||||
import { ChallengeDetail, EventStageDetail } from "./mocDetail"
|
||||
|
||||
export interface PFDetail {
|
||||
Id: number
|
||||
Name: string
|
||||
Buff: BuffDetail
|
||||
Option: OptionDetail[]
|
||||
SubOption: OptionDetail[]
|
||||
BeginTime: string
|
||||
EndTime: string
|
||||
Level: PFLevel[]
|
||||
export interface PFGroupDetail {
|
||||
ID: number;
|
||||
ChallengeGroupType: string;
|
||||
Name: Record<string, string>;
|
||||
Image: PFGroupImage;
|
||||
BeginTime: string;
|
||||
EndTime: string;
|
||||
SubOption: MazeBuff[];
|
||||
Option: MazeBuff[];
|
||||
Level: LevelData[];
|
||||
}
|
||||
|
||||
export interface BuffDetail {
|
||||
Name?: string | null
|
||||
Desc?: string | null
|
||||
Param: number[]
|
||||
export interface PFGroupImage {
|
||||
BackGroundPath: string;
|
||||
TabPicPath: string;
|
||||
TabPicSelectPath: string;
|
||||
ThemePicPath: string;
|
||||
}
|
||||
|
||||
export interface OptionDetail {
|
||||
Name: string
|
||||
Desc: string
|
||||
Param: number[]
|
||||
export interface MazeBuff {
|
||||
ID: number;
|
||||
Param: number[];
|
||||
Icon: string;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PFLevel {
|
||||
Id: number
|
||||
Name: string
|
||||
Challenge: ChallengeDetail[]
|
||||
DamageType1: string[]
|
||||
DamageType2: string[]
|
||||
MazeGroupID1: number
|
||||
MazeGroupID2: number
|
||||
NpcMonsterIDList1: number[]
|
||||
NpcMonsterIDList2: number[]
|
||||
EventIDList1: EventStageDetail[]
|
||||
EventIDList2: EventStageDetail[]
|
||||
InfiniteList1: Record<string, InfiniteWave>
|
||||
InfiniteList2: Record<string, InfiniteWave>
|
||||
export interface LevelData {
|
||||
Floor: number;
|
||||
ID: number;
|
||||
StageNum: number;
|
||||
Name: Record<string, string>;
|
||||
Target: StoryTarget[];
|
||||
DamageType1: string[];
|
||||
DamageType2: string[];
|
||||
MazeBuff: MazeBuff[];
|
||||
EventList1: StageConfig[];
|
||||
EventList2: StageConfig[];
|
||||
TurnLimit: number;
|
||||
BattleTarget: BattleTarget[];
|
||||
ClearScore: number;
|
||||
}
|
||||
|
||||
export interface StoryTarget {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Param: number[];
|
||||
}
|
||||
|
||||
export interface BattleTarget {
|
||||
ID: number;
|
||||
Param: number[];
|
||||
Name: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface StageConfig {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
HardLevelGroup: number;
|
||||
EliteGroup: number;
|
||||
Level: number;
|
||||
Release: boolean;
|
||||
MonsterList: number[][];
|
||||
Infinite?: InfiniteWave[] | null;
|
||||
}
|
||||
|
||||
export interface InfiniteWave {
|
||||
InfiniteWaveID: number
|
||||
MonsterGroupIDList: number[]
|
||||
MaxMonsterCount: number
|
||||
MaxTeammateCount: number
|
||||
Ability: string
|
||||
ParamList: number[]
|
||||
ClearPreviousAbility: boolean
|
||||
EliteGroup: number
|
||||
}
|
||||
ID: number;
|
||||
MaxMonsterCount: number;
|
||||
MonsterList: number[];
|
||||
EliteGroup: number;
|
||||
}
|
||||
@@ -1,19 +1,59 @@
|
||||
export interface PartData {
|
||||
Name: string;
|
||||
Desc: string;
|
||||
Story: string;
|
||||
}
|
||||
|
||||
export interface RequireBonus {
|
||||
Desc: string;
|
||||
ParamList: number[];
|
||||
}
|
||||
|
||||
export interface RelicDetail {
|
||||
Name: string;
|
||||
Icon: string;
|
||||
Parts: Record<string, PartData>;
|
||||
RequireNum: Record<string, RequireBonus>;
|
||||
Bonus: Record<string, { type: string, value: number }[]>
|
||||
|
||||
export interface RelicSetDetail {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
Image: RelicSetImage;
|
||||
Release: boolean;
|
||||
ReleaseVersion: string;
|
||||
IsPlanarSuit: boolean;
|
||||
DisplayItemID: number;
|
||||
DisplayItemIDRarity4: number;
|
||||
SkillList: number[];
|
||||
Skills: Record<string, RelicSkill>;
|
||||
Parts: Record<string, RelicPart>;
|
||||
}
|
||||
|
||||
export interface RelicSetImage {
|
||||
SetIconPath: string;
|
||||
SetIconFigurePath: string;
|
||||
}
|
||||
|
||||
export interface RelicSkill {
|
||||
RequireNum: number;
|
||||
Desc: Record<string, string>;
|
||||
AbilityName: string;
|
||||
Param: number[];
|
||||
Bonus: RelicSkillBonus[];
|
||||
}
|
||||
|
||||
export interface RelicSkillBonus {
|
||||
PropertyType: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
export interface RelicPart {
|
||||
Type: string;
|
||||
Image: RelicPartImage;
|
||||
Name: Record<string, string>;
|
||||
Desc: Record<string, string>;
|
||||
Data: Record<string, RelicPartData> | null;
|
||||
}
|
||||
|
||||
export interface RelicPartImage {
|
||||
IconPath: string;
|
||||
ItemFigureIconPath: string;
|
||||
}
|
||||
|
||||
export interface RelicPartData {
|
||||
ID: number;
|
||||
SetID: number;
|
||||
Type: string;
|
||||
Rarity: string;
|
||||
MainAffixGroup: number;
|
||||
SubAffixGroup: number;
|
||||
MaxLevel: number;
|
||||
ExpType: number;
|
||||
ExpProvide: number;
|
||||
CoinCost: number;
|
||||
Mode: string;
|
||||
}
|
||||
Reference in New Issue
Block a user