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

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