feat: new data
Gitea Auto Deploy / Deploy-Container (push) Failing after 53s

This commit is contained in:
2026-06-02 18:24:12 +07:00
parent ee101f3851
commit 796969a174
60 changed files with 1126 additions and 984 deletions
+3 -1
View File
@@ -79,6 +79,8 @@ export default function Header() {
if (cookieLocale) {
if (!listCurrentLanguageApi.hasOwnProperty(cookieLocale)) {
setLocale("en")
document.cookie = "MYNEXTAPP_LOCALE=en;"
router.refresh()
} else {
setLocale(cookieLocale)
}
@@ -614,4 +616,4 @@ export default function Header() {
))}
</div>
)
}
}
+24 -9
View File
@@ -71,8 +71,11 @@ export default function CopyImport() {
const selectAll = () => {
if (avatarCopySelected) {
setSelectedProfiles(avatars[avatarCopySelected?.ID.toString()].profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
const sourceAvatar = avatars[avatarCopySelected.ID.toString()]
const sourceProfiles = sourceAvatar?.profileList ?? []
setSelectedProfiles(sourceProfiles.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics ?? {}).length == 0) {
return null;
}
return {
@@ -110,8 +113,20 @@ export default function CopyImport() {
return;
}
const newListProfile = avatars[avatarCopySelected.ID.toString()].profileList.map((profile) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
const sourceAvatar = avatars[avatarCopySelected.ID.toString()]
const targetAvatar = avatars[avatarSelected.ID.toString()]
if (!sourceAvatar || !targetAvatar) {
setMessage({
type: "error",
text: transI18n("noDataToImport")
});
return;
}
const selectedKeys = new Set(selectedProfiles.map((profile) => profile.key))
const newListProfile = sourceAvatar.profileList.map((profile, index) => {
if (!selectedKeys.has(index) || (!profile.lightcone?.item_id && Object.keys(profile.relics ?? {}).length == 0)) {
return null;
}
return {
@@ -121,9 +136,9 @@ export default function CopyImport() {
}).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,
...targetAvatar,
profileList: targetAvatar.profileList.concat(newListProfile),
profileSelect: targetAvatar.profileList.length + newListProfile.length - 1,
}
setAvatar(newAvatar);
setSelectedProfiles([]);
@@ -275,7 +290,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) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
if (!profile.lightcone?.item_id && Object.keys(profile.relics ?? {}).length == 0) {
return null;
}
return (
@@ -291,4 +306,4 @@ export default function CopyImport() {
</div>
</div>
)
}
}
+53 -63
View File
@@ -9,6 +9,28 @@ import { converterOneEnkaDataToAvatarStore } from "@/helper";
import useModelStore from "@/stores/modelStore";
import { toast } from "react-toastify";
import { useTranslations } from "next-intl";
import { avatarEnkaDetailSchema, enkaResponseSchema } from "@/zod";
import { z } from "zod";
import useDetailDataStore from "@/stores/detailDataStore";
type AvatarEnkaDetailInput = z.infer<typeof avatarEnkaDetailSchema>;
const toCharacterInfoCard = (character: AvatarEnkaDetailInput): CharacterInfoCardType => ({
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: {
level: character.equipment?.level ?? 0,
rank: character.equipment?.rank ?? 0,
item_id: character.equipment?.tid ?? 0,
},
relics: character.relicList.map((relic) => ({
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
});
export default function EnkaImport() {
const {
@@ -22,8 +44,10 @@ export default function EnkaImport() {
const transI18n = useTranslations("DataPage")
const { avatars, setAvatar } = useUserDataStore();
const { setIsOpenImport } = useModelStore()
const { mapAvatar } = useDetailDataStore()
const [isLoading, setIsLoading] = useState(false)
const [Error, setError] = useState("")
const validCharacters = enkaData?.detailInfo.avatarDetailList.filter((character) => mapAvatar?.[character.avatarId]) ?? []
const handlerFetchData = async () => {
if (!uidInput) {
@@ -31,32 +55,27 @@ export default function EnkaImport() {
return;
}
setIsLoading(true)
const data : EnkaResponse = await SendDataThroughProxy({data: {serverUrl: "https://enka.network/api/hsr/uid/" + uidInput, method: "GET"}})
if (data) {
setEnkaData(data)
setSelectedCharacters(data.detailInfo.avatarDetailList.map((character) => {
return {
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: {
level: character.equipment?.level ?? 0,
rank: character.equipment?.rank ?? 0,
item_id: character.equipment?.tid ?? 0,
},
relics: character.relicList.map((relic) => ({
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
} as CharacterInfoCardType
}))
try {
const data: unknown = await SendDataThroughProxy({data: {serverUrl: "https://enka.network/api/hsr/uid/" + uidInput, method: "GET"}})
const parsed = enkaResponseSchema.safeParse(data)
if (!parsed.success) {
setEnkaData(null)
setSelectedCharacters([])
setError(transI18n("failedToFetchEnkaData"))
return
}
setEnkaData(parsed.data as EnkaResponse)
setSelectedCharacters(parsed.data.detailInfo.avatarDetailList.filter((character) => mapAvatar?.[character.avatarId]).map(toCharacterInfoCard))
setError("")
} else {
} catch {
setEnkaData(null)
setSelectedCharacters([])
setError(transI18n("failedToFetchEnkaData"))
} finally {
setIsLoading(false)
}
setIsLoading(false)
}
const handleCharacterToggle = (character: CharacterInfoCardType) => {
@@ -73,26 +92,7 @@ export default function EnkaImport() {
const selectAll = () => {
if (enkaData) {
setSelectedCharacters(enkaData?.detailInfo.avatarDetailList.map((character) => {
return {
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: (character.equipment && character.equipment.tid) ? {
level: character.equipment?.level ?? 0,
rank: character.equipment?.rank ?? 0,
item_id: character.equipment?.tid ?? 0,
} : null,
relics: character.relicList.map((relic) => {
return {
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
}
}),
} as CharacterInfoCardType
}));
setSelectedCharacters(validCharacters.map(toCharacterInfoCard));
}
};
@@ -107,7 +107,13 @@ export default function EnkaImport() {
}
setError("");
const listAvatars = { ...avatars }
const filterData = enkaData.detailInfo.avatarDetailList.filter((character) => selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatarId))
const parsed = enkaResponseSchema.safeParse(enkaData)
if (!parsed.success) {
setError(transI18n("failedToFetchEnkaData"));
return;
}
const filterData = parsed.data.detailInfo.avatarDetailList.filter((character) => mapAvatar?.[character.avatarId] && selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatarId))
filterData.forEach((character) => {
const newAvatar = { ...listAvatars[character.avatarId.toString()] }
if (Object.keys(newAvatar).length !== 0) {
@@ -193,26 +199,10 @@ export default function EnkaImport() {
)}
{/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{enkaData?.detailInfo.avatarDetailList.map((character) => (
{validCharacters.map((character) => (
<CharacterInfoCard
key={character.avatarId}
character={{
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level ?? 0,
lightcone: {
level: character.equipment?.level ?? 0,
rank: character.equipment?.rank ?? 0,
item_id: character.equipment?.tid ?? 0,
},
relics: character.relicList.map((relic) => ({
level: relic.level ?? 0,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
} as CharacterInfoCardType
}
character={toCharacterInfoCard(character)}
selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle}
/>
@@ -221,4 +211,4 @@ export default function EnkaImport() {
</div>
</div>
);
}
}
+59 -68
View File
@@ -11,6 +11,31 @@ import { toast } from "react-toastify";
import { converterOneFreeSRDataToAvatarStore } from "@/helper";
import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore";
import { z } from "zod";
type FreeSRJsonInput = z.infer<typeof freeSrJsonSchema>;
const toCharacterInfoCard = (data: FreeSRJsonInput, character: FreeSRJsonInput["avatars"][string]): CharacterInfoCardType => {
const lightcone = data.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = data.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return {
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? 0,
},
relics: relics.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})),
}
}
export default function FreeSRImport() {
const { avatars, setAvatar } = useUserDataStore();
@@ -20,6 +45,8 @@ export default function FreeSRImport() {
const [Error, setError] = useState("")
const { freeSRData, setFreeSRData, selectedCharacters, setSelectedCharacters } = useFreeSRStore()
const transI18n = useTranslations("DataPage")
const parsedFreeSRData = freeSrJsonSchema.safeParse(freeSRData)
const validFreeSRData = parsedFreeSRData.success ? parsedFreeSRData.data : null
const handleCharacterToggle = (character: CharacterInfoCardType) => {
if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) {
@@ -34,27 +61,9 @@ export default function FreeSRImport() {
};
const selectAll = () => {
if (freeSRData) {
setSelectedCharacters(Object.values(freeSRData?.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
const lightcone = freeSRData.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = freeSRData.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return {
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})),
} as CharacterInfoCardType
}));
const parsed = freeSrJsonSchema.safeParse(freeSRData)
if (parsed.success) {
setSelectedCharacters(Object.values(parsed.data.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => toCharacterInfoCard(parsed.data, character)));
}
};
@@ -82,41 +91,35 @@ export default function FreeSRImport() {
reader.onload = (e) => {
try {
const data = JSON.parse(e.target?.result as string);
const parsed = freeSrJsonSchema.parse(data)
setFreeSRData(parsed)
const parsed = freeSrJsonSchema.safeParse(data)
if (!parsed.success) {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("fileMustBeAValidJsonFile"))
return
}
setFreeSRData(parsed.data)
setError("")
setSelectedCharacters(Object.values(parsed?.avatars || {}).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
const lightcone = parsed?.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = parsed?.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return {
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics?.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})) ?? [],
} as CharacterInfoCardType
}));
setSelectedCharacters(Object.values(parsed.data.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => toCharacterInfoCard(parsed.data, character)));
} catch {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("fileMustBeAValidJsonFile"))
} finally {
setIsLoading(false)
}
};
reader.onerror = () => {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("fileMustBeAValidJsonFile"))
setIsLoading(false)
}
reader.readAsText(file);
setIsLoading(false)
}
setIsLoading(false)
};
const handleImport = () => {
@@ -130,8 +133,14 @@ export default function FreeSRImport() {
}
setError("");
const parsed = freeSrJsonSchema.safeParse(freeSRData)
if (!parsed.success) {
setError(transI18n("fileMustBeAValidJsonFile"));
return;
}
const listAvatars = { ...avatars }
const filterData = Object.values(freeSRData?.avatars || {}).filter((character) => selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id))
const filterData = Object.values(parsed.data.avatars).filter((character) => selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id))
filterData.forEach((character) => {
const newAvatar = { ...listAvatars[character.avatar_id] }
if (Object.keys(newAvatar).length !== 0) {
@@ -141,7 +150,7 @@ export default function FreeSRImport() {
rank: character.data.rank ?? 0,
skills: character.data.skills
}
const newProfile = converterOneFreeSRDataToAvatarStore(freeSRData, newAvatar.profileList.length, character.avatar_id)
const newProfile = converterOneFreeSRDataToAvatarStore(parsed.data, newAvatar.profileList.length, character.avatar_id)
if (newProfile) {
newAvatar.profileList.push(newProfile)
newAvatar.profileSelect = newAvatar.profileList.length - 1
@@ -202,29 +211,11 @@ export default function FreeSRImport() {
)}
{/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{Object.values(freeSRData?.avatars || {}).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
const lightcone = freeSRData?.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = freeSRData?.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
{validFreeSRData && Object.values(validFreeSRData.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
return (
<CharacterInfoCard
key={character.avatar_id}
character={{
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics?.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})) ?? [],
} as CharacterInfoCardType
}
character={toCharacterInfoCard(validFreeSRData, character)}
selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle}
/>
@@ -234,4 +225,4 @@ export default function FreeSRImport() {
</div>
</div>
)
}
}
+143 -219
View File
@@ -5,7 +5,7 @@ import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image";
import { MonsterStore } from "@/types";
import { ASEvent, MonsterStore } from "@/types";
import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore";
@@ -16,7 +16,6 @@ export default function AsBar() {
setAsConfig
} = useUserDataStore()
const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => {
@@ -27,20 +26,52 @@ export default function AsBar() {
return mapAS[as_config.event_id.toString()]
}, [as_config, mapAS])
const floorSideList = useMemo(() => {
if (!eventSelected) return [];
const floorList = [
{
id: "firstNode",
name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies")
},
{
id: "secondNode",
name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies")
},
]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === as_config.challenge_id) {
floorList.push({
id: "thirdNode",
name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies")
})
}
return floorList
}, [as_config.challenge_id, eventSelected, transI18n])
const buffList = useMemo(() => {
if (!eventSelected) return [];
if (as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") {
if (as_config.floor_side === "firstNode") {
return eventSelected?.BuffList1 ?? [];
}
if (as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") {
if (as_config.floor_side === "secondNode") {
return eventSelected?.BuffList2 ?? [];
}
if (as_config.floor_side === "thirdNode" && eventSelected?.BuffList3) {
return eventSelected?.BuffList3 ?? [];
}
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)
@@ -65,67 +96,36 @@ export default function AsBar() {
newBattleConfig.monsters = []
newBattleConfig.stage_id = 0
if ((as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower")
&& challengeSelected.EventList1.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
for (const wave of challengeSelected.EventList1[0].MonsterList) {
let targetEventList: ASEvent[] = []
if (as_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1
} else if (as_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2
} else if (as_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList
}
if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[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 ((as_config.floor_side === "Lower" || as_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 (as_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 (as_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,
level: targetEventList[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
setAsConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
challengeSelected,
eventSelected,
mapAS,
as_config.event_id,
as_config.challenge_id,
@@ -186,10 +186,9 @@ export default function AsBar() {
onChange={(e) => setAsConfig({ ...as_config, floor_side: e.target.value })}
>
<option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option>
<option value="Lower">{transI18n("lower")}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option>
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
{floorSideList.map((side) => {
return <option key={side.id} value={side.id}>{side.name}</option>
})}
</select>
</div>
</div>
@@ -233,182 +232,107 @@ export default function AsBar() {
{/* Enemy Waves */}
{(as_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* First Half */}
<div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => {
const eventList = side.id === "firstNode"
? challengeSelected?.EventList1
: side.id === "secondNode"
? challengeSelected?.EventList2
: side.id === "thirdNode"
? eventSelected?.Tierce?.EventList
: [];
{challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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>
if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0];
<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={`${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>
return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
<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>
{targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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()],
targetEvent?.EliteGroup,
targetEvent?.HardLevelGroup,
targetEvent?.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. {targetEvent.Level}
</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) => (
<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
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"
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>
</div>
))}
</div>
{/* Second Half */}
<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?.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="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="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={`${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>
))}
</div>
)})}
</div>
)}
+140 -214
View File
@@ -7,7 +7,7 @@ import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image";
import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types";
import { MoCEvent, MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore";
export default function MocBar() {
@@ -28,6 +28,33 @@ export default function MocBar() {
return mapMoc[moc_config.event_id.toString()]
}, [moc_config, mapMoc])
const floorSideList = useMemo(() => {
if (!eventSelected) return [];
const floorList = [
{
id: "firstNode",
name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies")
},
{
id: "secondNode",
name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies")
},
]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === moc_config.challenge_id) {
floorList.push({
id: "thirdNode",
name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies")
})
}
return floorList
}, [moc_config.challenge_id, eventSelected, transI18n])
useEffect(() => {
if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
@@ -47,62 +74,36 @@ export default function MocBar() {
}
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) {
let targetEventList: MoCEvent[] = []
if (moc_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1
} else if (moc_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2
} else if (moc_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList
}
if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[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,
level: targetEventList[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
setMocConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
challengeSelected,
eventSelected,
moc_config.event_id,
moc_config.challenge_id,
moc_config.floor_side,
@@ -168,10 +169,9 @@ export default function MocBar() {
onChange={(e) => setMocConfig({ ...moc_config, floor_side: e.target.value })}
>
<option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option>
<option value="Lower">{transI18n("lower")}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option>
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
{floorSideList.map((side) => (
<option key={side.id} value={side.id}>{side.name}</option>
))}
</select>
</div>
@@ -232,182 +232,108 @@ export default function MocBar() {
{/* Enemy Waves */}
{(moc_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* First Half */}
<div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => {
const eventList = side.id === "firstNode"
? challengeSelected?.EventList1
: side.id === "secondNode"
? challengeSelected?.EventList2
: side.id === "thirdNode"
? eventSelected?.Tierce?.EventList
: [];
{challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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>
if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0];
<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={`${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>
return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
<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>
{targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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()],
targetEvent?.EliteGroup,
targetEvent?.HardLevelGroup,
targetEvent?.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. {targetEvent.Level}
</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 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={`${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 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 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 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>
))}
</div>
))}
</div>
{/* Second Half */}
<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?.EventList2?.length > 0 && challengeSelected?.EventList2?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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="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={`${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>
)
})}
</div>
)}
+141 -216
View File
@@ -5,7 +5,7 @@ import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image";
import { MonsterStore } from "@/types";
import { MonsterStore, PFEvent } from "@/types";
import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore";
@@ -26,6 +26,33 @@ export default function PfBar() {
return mapPF[pf_config.event_id.toString()]
}, [pf_config, mapPF])
const floorSideList = useMemo(() => {
if (!eventSelected) return [];
const floorList = [
{
id: "firstNode",
name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies")
},
{
id: "secondNode",
name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies")
},
]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === pf_config.challenge_id) {
floorList.push({
id: "thirdNode",
name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies")
})
}
return floorList
}, [pf_config.challenge_id, eventSelected, transI18n])
useEffect(() => {
if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) {
@@ -50,63 +77,36 @@ export default function PfBar() {
}
newBattleConfig.monsters = []
newBattleConfig.stage_id = 0
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) {
let targetEventList: PFEvent[] = []
if (pf_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1
} else if (pf_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2
} else if (pf_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList
}
if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[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 ((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: value,
level: challengeSelected.EventList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
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: value,
level: challengeSelected.EventList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
} 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: value,
level: challengeSelected.EventList2[0].Level,
monster_id: value as number,
level: targetEventList[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
setPfConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
challengeSelected,
eventSelected,
pf_config.event_id,
pf_config.challenge_id,
pf_config.floor_side,
@@ -167,10 +167,9 @@ export default function PfBar() {
onChange={(e) => setPfConfig({ ...pf_config, floor_side: e.target.value })}
>
<option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option>
<option value="Lower">{transI18n("lower")}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option>
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
{floorSideList.map((side) => (
<option key={side.id} value={side.id}>{side.name}</option>
))}
</select>
</div>
</div>
@@ -232,182 +231,108 @@ export default function PfBar() {
{/* Enemy Waves */}
{(pf_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* First Half */}
<div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("firstHalfEnemies")}</h2>
{challengeSelected && Object.values(challengeSelected.EventList1?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => {
const eventList = side.id === "firstNode"
? challengeSelected?.EventList1
: side.id === "secondNode"
? challengeSelected?.EventList2
: side.id === "thirdNode"
? eventSelected?.Tierce?.EventList
: [];
<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={`${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>
if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0];
<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>
return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-2 text-info">{side.wave}</h2>
{targetEvent && Object.values(targetEvent.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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,
targetEvent?.HardLevelGroup,
targetEvent?.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. {targetEvent.Level}
</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 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={`${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 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 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 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>
))}
</div>
))}
</div>
{/* Second Half */}
<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?.EventList2[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6">
<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="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={`${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>
)
})}
</div>
)}
+11 -11
View File
@@ -278,33 +278,33 @@ export default function QuickView() {
if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
statsData.HP.value += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHP,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level
)
statsData.HP.base += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightCone?.[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(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightCone?.[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(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightCone?.[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(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightCone?.[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(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightCone?.[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
)
+6 -6
View File
@@ -95,20 +95,20 @@ export default function ShowCaseInfo() {
if (!avatarSelected || !avatarProfile?.lightcone || !mapLightCone[avatarProfile?.lightcone?.item_id]) return
const promotion = calcPromotion(avatarProfile?.lightcone?.level)
const atkStat = calcBaseStat(
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttack,
mapLightCone[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(
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHP,
mapLightCone[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(
mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefence,
mapLightCone[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
)