UPDATE: New monster data
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m20s

This commit is contained in:
2026-03-17 00:45:23 +07:00
parent d27b96d023
commit 319ad79233
125 changed files with 2718 additions and 917593 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1 @@
export * from "./cache"

View File

@@ -1,4 +1,4 @@
export * from "./useFetchConfigData";
export * from "./useFetchMetaData";
export * from "./useFetchAvatarData";
export * from "./useFetchLightconeData";
export * from "./useFetchRelicData";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
export type AffixDetail = {
property: string;
base: number;
step: number;
step_num: number;
}

View File

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

View File

@@ -1,8 +0,0 @@
export interface CharacterBasic {
id: string;
icon: string;
rank: string;
baseType: string;
damageType: string;
lang: Record<string, string>;
}

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
export interface EventBasic {
id: string;
begin: string;
end: string;
lang: Record<string, string>;
}

View File

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

View File

@@ -1,8 +0,0 @@
export interface LightConeBasic {
id: string;
rank: string;
thumbnail: string
image: string,
baseType: string;
lang: Record<string, string>;
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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