This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+21
-6
@@ -24,7 +24,10 @@ export function calcPromotion(level: number) {
|
||||
}
|
||||
|
||||
|
||||
export function calcRarity(rarity: string) {
|
||||
export function calcRarity(rarity?: string) {
|
||||
if (!rarity) {
|
||||
return 1
|
||||
}
|
||||
if (rarity.includes("5")) {
|
||||
return 5
|
||||
}
|
||||
@@ -40,11 +43,12 @@ export function calcRarity(rarity: string) {
|
||||
export function calcMainAffixBonus(affix?: MainAffixData, level?: number) {
|
||||
if (!affix || typeof level !== "number") return "0"
|
||||
const value = affix.BaseValue + affix.LevelAdd * level;
|
||||
const statMeta = mappingStats[affix.Property]
|
||||
|
||||
if (mappingStats?.[affix.Property].unit === "%") {
|
||||
if (statMeta?.unit === "%") {
|
||||
return (value * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.Property].name === "SPD") {
|
||||
if (statMeta?.name === "SPD") {
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
@@ -53,10 +57,11 @@ export function calcMainAffixBonus(affix?: MainAffixData, level?: number) {
|
||||
|
||||
export const calcAffixBonus = (affix?: SubAffixData, stepCount?: number, rollCount?: number) => {
|
||||
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number") return "0"
|
||||
if (mappingStats?.[affix.Property].unit === "%") {
|
||||
const statMeta = mappingStats[affix.Property]
|
||||
if (statMeta?.unit === "%") {
|
||||
return ((affix.BaseValue * rollCount + affix.StepValue * stepCount) * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.Property].name === "SPD") {
|
||||
if (statMeta?.name === "SPD") {
|
||||
return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(1);
|
||||
}
|
||||
return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(0);
|
||||
@@ -107,6 +112,16 @@ export const calcMonsterStats = (
|
||||
hardLevelConfig: Record<string, Record<string, HardLevelData>>,
|
||||
eliteConfig: Record<string, EliteData>
|
||||
) => {
|
||||
if (!monster?.Base || !monster?.Modify) {
|
||||
return {
|
||||
atk: 0,
|
||||
def: 0,
|
||||
hp: 0,
|
||||
spd: 0,
|
||||
stance: 0,
|
||||
}
|
||||
}
|
||||
|
||||
let hardLevelRatio = {
|
||||
AttackRatio: 1,
|
||||
DefenceRatio:1,
|
||||
@@ -135,4 +150,4 @@ export const calcMonsterStats = (
|
||||
spd: monster.Base.SpeedBase * monster.Modify.SpeedModifyRatio * hardLevelRatio.SpeedRatio * eliteRatio.SpeedRatio,
|
||||
stance: (monster.Base.StanceBase * monster.Modify.StanceModifyRatio * hardLevelRatio.StanceRatio * eliteRatio.StanceRatio) / 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function converterToAvatarStore(data: Record<string, AvatarDetail>): { [k
|
||||
avatar_id: Number(key),
|
||||
data: {
|
||||
rank: 0,
|
||||
skills: Object.values(value.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
skills: Object.values(value.SkillTrees ?? {}).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
@@ -42,7 +42,8 @@ export function converterToAvatarStore(data: Record<string, AvatarDetail>): { [k
|
||||
}
|
||||
|
||||
export function converterOneEnkaDataToAvatarStore(data: AvatarEnkaDetail, count: number): AvatarProfileStore | null {
|
||||
if (!data.equipment && (!data.relicList || data.relicList.length === 0)) return null
|
||||
const relicList = data.relicList ?? []
|
||||
if (!data.equipment && relicList.length === 0) return null
|
||||
const profile: AvatarProfileStore = {
|
||||
profile_name: `Enka Profile ${count}`,
|
||||
lightcone: (data.equipment && data.equipment.tid) ? {
|
||||
@@ -51,12 +52,12 @@ export function converterOneEnkaDataToAvatarStore(data: AvatarEnkaDetail, count:
|
||||
rank: data.equipment?.rank ?? 0,
|
||||
promotion: data.equipment?.promotion ?? 0,
|
||||
} : null,
|
||||
relics: Object.fromEntries(data.relicList.map((relic) => [relic.tid.toString()[relic.tid.toString().length - 1], {
|
||||
relics: Object.fromEntries(relicList.map((relic) => [relic.tid.toString()[relic.tid.toString().length - 1], {
|
||||
level: relic.level ?? 0,
|
||||
relic_id: relic.tid,
|
||||
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
|
||||
main_affix_id: relic.mainAffixId,
|
||||
sub_affixes: relic.subAffixList.map((subAffix) => ({
|
||||
sub_affixes: (relic.subAffixList ?? []).map((subAffix) => ({
|
||||
sub_affix_id: subAffix.affixId,
|
||||
count: subAffix.cnt,
|
||||
step: subAffix.step ?? 0
|
||||
@@ -68,8 +69,8 @@ export function converterOneEnkaDataToAvatarStore(data: AvatarEnkaDetail, count:
|
||||
|
||||
|
||||
export function converterOneFreeSRDataToAvatarStore(data: FreeSRJson, count: number , avatar_id: number): AvatarProfileStore | null {
|
||||
const lightcone = data.lightcones.find((lightcone) => lightcone.equip_avatar === avatar_id)
|
||||
const relics = data.relics.filter((relic) => relic.equip_avatar === avatar_id)
|
||||
const lightcone = (data.lightcones ?? []).find((lightcone) => lightcone.equip_avatar === avatar_id)
|
||||
const relics = (data.relics ?? []).filter((relic) => relic.equip_avatar === avatar_id)
|
||||
if (!lightcone && (!relics || relics.length === 0)) return null
|
||||
const relicsMap = {} as Record<string, RelicStore>
|
||||
|
||||
@@ -79,7 +80,7 @@ export function converterOneFreeSRDataToAvatarStore(data: FreeSRJson, count: num
|
||||
relic_id: relic.relic_id,
|
||||
relic_set_id: relic.relic_set_id,
|
||||
main_affix_id: relic.main_affix_id,
|
||||
sub_affixes: relic.sub_affixes.map((subAffix) => ({
|
||||
sub_affixes: (relic.sub_affixes ?? []).map((subAffix) => ({
|
||||
sub_affix_id: subAffix.sub_affix_id,
|
||||
count: subAffix.count,
|
||||
step: subAffix.step ?? 0
|
||||
|
||||
@@ -84,7 +84,9 @@ export function converterToFreeSRJson(
|
||||
let internalUidLightcone = 0
|
||||
let internalUidRelic = 0
|
||||
|
||||
Object.entries(avatars).forEach(([avatarId, avatar]) => {
|
||||
Object.entries(avatars ?? {}).forEach(([avatarId, avatar]) => {
|
||||
if (!avatar) return
|
||||
|
||||
const skillsByAnchorType: Record<string, number> = {}
|
||||
for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) {
|
||||
if (skillConfig?.[skillId]) {
|
||||
@@ -95,17 +97,20 @@ export function converterToFreeSRJson(
|
||||
owner_uid: Number(avatar.owner_uid || 0),
|
||||
avatar_id: Number(avatar.avatar_id || 0),
|
||||
data: {
|
||||
rank: Number(avatar.data.rank || 0),
|
||||
skills: avatar.data.skills,
|
||||
rank: Number(avatar.data?.rank || 0),
|
||||
skills: avatar.data?.skills ?? {},
|
||||
skills_by_anchor_type: Object.keys(skillsByAnchorType).length > 0 ? skillsByAnchorType : undefined,
|
||||
},
|
||||
level: Number(avatar.level || 0),
|
||||
promotion: Number(avatar.promotion || 0),
|
||||
techniques: avatar.techniques,
|
||||
techniques: avatar.techniques ?? [],
|
||||
sp_value: Number(avatar.sp_value || 0),
|
||||
sp_max: Number(avatar.sp_max || 0),
|
||||
enhanced_id: avatar.enhanced ? Number(avatar.enhanced || 0) : undefined,
|
||||
}
|
||||
const currentProfile = avatar.profileList[avatar.profileSelect]
|
||||
const currentProfile = avatar.profileList?.[avatar.profileSelect] ?? avatar.profileList?.[0]
|
||||
if (!currentProfile) return
|
||||
|
||||
if (currentProfile.lightcone && currentProfile.lightcone.item_id !== 0) {
|
||||
const newLightcone: LightconeJson = {
|
||||
level: Number(currentProfile.lightcone.level || 0),
|
||||
@@ -128,7 +133,7 @@ export function converterToFreeSRJson(
|
||||
relic_id: Number(relic.relic_id || 0),
|
||||
relic_set_id: Number(relic.relic_set_id || 0),
|
||||
main_affix_id: Number(relic.main_affix_id || 0),
|
||||
sub_affixes: relic.sub_affixes,
|
||||
sub_affixes: relic.sub_affixes ?? [],
|
||||
internal_uid: internalUidRelic,
|
||||
equip_avatar: Number(avatar.avatar_id || 0),
|
||||
}
|
||||
@@ -146,4 +151,4 @@ export function converterToFreeSRJson(
|
||||
avatars: avatarsJson,
|
||||
battle_config: battleJson,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ export function getLocaleName(locale: string, data: Record<string, string> | und
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
const langKey = listCurrentLanguageApi[locale as keyof typeof listCurrentLanguageApi].toLowerCase();
|
||||
|
||||
let text = data[langKey] ?? "";
|
||||
|
||||
@@ -12,7 +12,7 @@ export function getSkillTree(avatarSelected: AvatarDetail | null, enhanced: stri
|
||||
}, {} as Record<string, number>)
|
||||
}
|
||||
|
||||
return Object.values(avatarSelected?.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
return Object.values(avatarSelected?.SkillTrees ?? {}).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
|
||||
+19
-11
@@ -1,15 +1,23 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function downloadJson(fileName: string, data: any) {
|
||||
const json = JSON.stringify(data, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
export function downloadJson(fileName: string, data: any): boolean {
|
||||
if (typeof document === "undefined") return false
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${fileName}.json`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
try {
|
||||
const json = JSON.stringify(data, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${fileName}.json`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("Failed to download JSON:", error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export function randomPartition(sum: number, parts: number): number[] {
|
||||
if (!Number.isFinite(sum) || !Number.isInteger(parts) || parts <= 0) {
|
||||
return [];
|
||||
}
|
||||
if (sum < parts) {
|
||||
return Array.from({ length: parts }, () => 0);
|
||||
}
|
||||
|
||||
const raw = Array.from({ length: parts }, () => Math.random());
|
||||
const total = raw.reduce((a, b) => a + b, 0);
|
||||
const result = raw.map(r => Math.floor((r / total) * (sum - parts)) + 1);
|
||||
@@ -24,4 +31,4 @@ export function randomStep(x: number): number {
|
||||
total += Math.floor(Math.random() * 3);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ const formatValue = (value: number, format: string, floatDigits?: string, hasPer
|
||||
return String(value);
|
||||
};
|
||||
|
||||
export function replaceByParam(desc: string, params: number[]): string {
|
||||
export function replaceByParam(desc?: string, params: number[] = []): string {
|
||||
if (!desc) return "";
|
||||
|
||||
const PARAM_REGEX = /#(\d+)\[(f(\d+)|i)\](%)?/g;
|
||||
|
||||
@@ -35,4 +36,4 @@ export function replaceByParam(desc: string, params: number[]): string {
|
||||
result = result.replace(PARAM_REGEX, processor);
|
||||
|
||||
return result.split("\\n").join("<br/>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { connectPersistedSchema } from '@/zod';
|
||||
import { createValidatedJSONStorage } from './persistStorage';
|
||||
|
||||
|
||||
interface ConnectState {
|
||||
@@ -15,8 +17,10 @@ interface ConnectState {
|
||||
setPassword: (newPassword: string) => void;
|
||||
}
|
||||
|
||||
type ConnectPersistedState = Pick<ConnectState, "connectionType" | "privateType" | "serverUrl" | "username" | "password">;
|
||||
|
||||
const useConnectStore = create<ConnectState>()(
|
||||
persist(
|
||||
persist<ConnectState, [], [], ConnectPersistedState>(
|
||||
(set) => ({
|
||||
connectionType: "FireflyGo",
|
||||
privateType: "Local",
|
||||
@@ -31,9 +35,16 @@ const useConnectStore = create<ConnectState>()(
|
||||
}),
|
||||
{
|
||||
name: 'connect-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
storage: createValidatedJSONStorage(() => localStorage, connectPersistedSchema),
|
||||
partialize: (state) => ({
|
||||
connectionType: state.connectionType,
|
||||
privateType: state.privateType,
|
||||
serverUrl: state.serverUrl,
|
||||
username: state.username,
|
||||
password: state.password,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default useConnectStore;
|
||||
export default useConnectStore;
|
||||
|
||||
@@ -6,7 +6,7 @@ interface EnkaState {
|
||||
enkaData: EnkaResponse | null;
|
||||
uidInput: string;
|
||||
setSelectedCharacters: (newListAvatar: CharacterInfoCardType[]) => void;
|
||||
setEnkaData: (newEnkaData: EnkaResponse) => void;
|
||||
setEnkaData: (newEnkaData: EnkaResponse | null) => void;
|
||||
setUidInput: (newUidInput: string) => void;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const useEnkaStore = create<EnkaState>((set) => ({
|
||||
uidInput: "",
|
||||
setUidInput: (newUidInput: string) => set({ uidInput: newUidInput }),
|
||||
setSelectedCharacters: (newListAvatar: CharacterInfoCardType[]) => set({ selectedCharacters: newListAvatar }),
|
||||
setEnkaData: (newEnkaData: EnkaResponse) => set({ enkaData: newEnkaData }),
|
||||
setEnkaData: (newEnkaData: EnkaResponse | null) => set({ enkaData: newEnkaData }),
|
||||
}));
|
||||
|
||||
export default useEnkaStore;
|
||||
export default useEnkaStore;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ChangelogItemType } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { localePersistedSchema } from '@/zod';
|
||||
import { createValidatedJSONStorage } from './persistStorage';
|
||||
|
||||
|
||||
interface LocaleState {
|
||||
@@ -14,8 +16,10 @@ interface LocaleState {
|
||||
setLocale: (newLocale: string) => void;
|
||||
}
|
||||
|
||||
type LocalePersistedState = Pick<LocaleState, "locale" | "theme" | "currentVersion" | "changelog">;
|
||||
|
||||
const useLocaleStore = create<LocaleState>()(
|
||||
persist(
|
||||
persist<LocaleState, [], [], LocalePersistedState>(
|
||||
(set) => ({
|
||||
locale: "en",
|
||||
theme: "night",
|
||||
@@ -28,9 +32,15 @@ const useLocaleStore = create<LocaleState>()(
|
||||
}),
|
||||
{
|
||||
name: 'locale-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
storage: createValidatedJSONStorage(() => localStorage, localePersistedSchema),
|
||||
partialize: (state) => ({
|
||||
locale: state.locale,
|
||||
theme: state.theme,
|
||||
currentVersion: state.currentVersion,
|
||||
changelog: state.changelog,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default useLocaleStore;
|
||||
export default useLocaleStore;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { PersistStorage, StateStorage, StorageValue } from "zustand/middleware";
|
||||
import type { ZodType } from "zod";
|
||||
|
||||
export function createValidatedJSONStorage<S>(
|
||||
getStorage: () => StateStorage,
|
||||
stateSchema: ZodType<S>
|
||||
): PersistStorage<S> | undefined {
|
||||
let storage: StateStorage;
|
||||
|
||||
try {
|
||||
storage = getStorage();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const removeInvalidItem = (name: string, reason: string) => {
|
||||
console.warn(`Invalid persisted store "${name}" was cleared: ${reason}`);
|
||||
storage.removeItem(name);
|
||||
};
|
||||
|
||||
return {
|
||||
getItem: (name) => {
|
||||
const raw = storage.getItem(name);
|
||||
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw instanceof Promise) {
|
||||
return raw.then((value) => parseStorageValue(name, value, stateSchema, removeInvalidItem));
|
||||
}
|
||||
|
||||
return parseStorageValue(name, raw, stateSchema, removeInvalidItem);
|
||||
},
|
||||
setItem: (name, value) => {
|
||||
storage.setItem(name, JSON.stringify(value));
|
||||
},
|
||||
removeItem: (name) => {
|
||||
storage.removeItem(name);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseStorageValue<S>(
|
||||
name: string,
|
||||
raw: string | null,
|
||||
stateSchema: ZodType<S>,
|
||||
removeInvalidItem: (name: string, reason: string) => void
|
||||
): StorageValue<S> | null {
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
removeInvalidItem(name, "malformed JSON");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== "object" || !("state" in parsed)) {
|
||||
removeInvalidItem(name, "missing persisted state");
|
||||
return null;
|
||||
}
|
||||
|
||||
const storedValue = parsed as StorageValue<unknown>;
|
||||
const state = stateSchema.safeParse(storedValue.state);
|
||||
|
||||
if (!state.success) {
|
||||
removeInvalidItem(name, "state schema mismatch");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
state: state.data,
|
||||
version: typeof storedValue.version === "number" ? storedValue.version : undefined,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ASConfigStore, AvatarStore, CEConfigStore, MOCConfigStore, PEAKConfigStore, PFConfigStore } from '@/types';
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { micsSchema } from '@/zod';
|
||||
import { createValidatedJSONStorage } from './persistStorage';
|
||||
|
||||
|
||||
interface UserDataState {
|
||||
@@ -21,8 +23,19 @@ interface UserDataState {
|
||||
setCeConfig: (newCeConfig: CEConfigStore) => void;
|
||||
}
|
||||
|
||||
type UserDataPersistedState = Pick<
|
||||
UserDataState,
|
||||
| "avatars"
|
||||
| "battle_type"
|
||||
| "moc_config"
|
||||
| "pf_config"
|
||||
| "as_config"
|
||||
| "peak_config"
|
||||
| "ce_config"
|
||||
>;
|
||||
|
||||
const useUserDataStore = create<UserDataState>()(
|
||||
persist(
|
||||
persist<UserDataState, [], [], UserDataPersistedState>(
|
||||
(set) => ({
|
||||
avatars: {},
|
||||
battle_type: "",
|
||||
@@ -84,9 +97,18 @@ const useUserDataStore = create<UserDataState>()(
|
||||
}),
|
||||
{
|
||||
name: 'user-data-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
storage: createValidatedJSONStorage(() => localStorage, micsSchema),
|
||||
partialize: (state) => ({
|
||||
avatars: state.avatars,
|
||||
battle_type: state.battle_type,
|
||||
moc_config: state.moc_config,
|
||||
pf_config: state.pf_config,
|
||||
as_config: state.as_config,
|
||||
peak_config: state.peak_config,
|
||||
ce_config: state.ce_config,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default useUserDataStore;
|
||||
export default useUserDataStore;
|
||||
|
||||
@@ -9,7 +9,9 @@ export interface ASGroupDetail {
|
||||
EndTime: string;
|
||||
BuffList1: ASBuff[];
|
||||
BuffList2: ASBuff[];
|
||||
BuffList3: ASBuff[] | null;
|
||||
Level: ASLevel[];
|
||||
Tierce: ASTierceLevel | null;
|
||||
}
|
||||
|
||||
export interface ASBuff {
|
||||
@@ -21,6 +23,16 @@ export interface ASBuff {
|
||||
ExtraList?: ExtraEffect[];
|
||||
}
|
||||
|
||||
export interface ASTierceLevel {
|
||||
ID: number;
|
||||
PreChallenge: number;
|
||||
Name: Record<string, string>;
|
||||
Target: ASTarget[];
|
||||
DamageType: string[];
|
||||
TurnLimit: number;
|
||||
EventList: ASEvent[];
|
||||
}
|
||||
|
||||
export interface ASLevel {
|
||||
Floor: number;
|
||||
ID: number;
|
||||
@@ -35,6 +47,7 @@ export interface ASLevel {
|
||||
EventList2: ASEvent[];
|
||||
Monster1: ASMonster;
|
||||
Monster2: ASMonster;
|
||||
Monster3: ASMonster | null;
|
||||
}
|
||||
|
||||
export interface ASTarget {
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ interface FlatProp {
|
||||
|
||||
interface RelicFlat {
|
||||
props: FlatProp[]
|
||||
setName: string
|
||||
setName: string | number
|
||||
setID: number
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ interface SkillTree {
|
||||
|
||||
interface EquipmentFlat {
|
||||
props: FlatProp[]
|
||||
name: string
|
||||
name: string | number
|
||||
}
|
||||
|
||||
interface Equipment {
|
||||
|
||||
@@ -7,6 +7,17 @@ export interface MOCGroupDetail {
|
||||
BeginTime: string;
|
||||
EndTime: string;
|
||||
Level: MoCLevel[];
|
||||
Tierce: MoCTierceLevel | null;
|
||||
}
|
||||
|
||||
export interface MoCTierceLevel {
|
||||
ID: number;
|
||||
PreChallenge: number;
|
||||
Name: Record<string, string>;
|
||||
Target: MoCTarget[];
|
||||
DamageType: string[];
|
||||
TurnLimit: number;
|
||||
EventList: MoCEvent[];
|
||||
}
|
||||
|
||||
export interface MoCLevel {
|
||||
|
||||
+16
-5
@@ -6,7 +6,8 @@ export interface PFGroupDetail {
|
||||
EndTime: string;
|
||||
SubOption: MazeBuff[];
|
||||
Option: MazeBuff[];
|
||||
Level: LevelData[];
|
||||
Level: PFLevel[];
|
||||
Tierce: PFTierceLevel | null;
|
||||
}
|
||||
|
||||
export interface MazeBuff {
|
||||
@@ -17,7 +18,17 @@ export interface MazeBuff {
|
||||
Desc: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface LevelData {
|
||||
export interface PFTierceLevel {
|
||||
ID: number;
|
||||
PreChallenge: number;
|
||||
Name: Record<string, string>;
|
||||
Target: StoryTarget[];
|
||||
DamageType: string[];
|
||||
TurnLimit: number;
|
||||
EventList: PFEvent[];
|
||||
}
|
||||
|
||||
export interface PFLevel {
|
||||
Floor: number;
|
||||
ID: number;
|
||||
StageNum: number;
|
||||
@@ -26,8 +37,8 @@ export interface LevelData {
|
||||
DamageType1: string[];
|
||||
DamageType2: string[];
|
||||
MazeBuff: MazeBuff[];
|
||||
EventList1: StageConfig[];
|
||||
EventList2: StageConfig[];
|
||||
EventList1: PFEvent[];
|
||||
EventList2: PFEvent[];
|
||||
TurnLimit: number;
|
||||
BattleTarget: BattleTarget[];
|
||||
ClearScore: number;
|
||||
@@ -45,7 +56,7 @@ export interface BattleTarget {
|
||||
Name: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface StageConfig {
|
||||
export interface PFEvent {
|
||||
ID: number;
|
||||
Name: Record<string, string>;
|
||||
HardLevelGroup: number;
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface AvatarJson {
|
||||
techniques: number[];
|
||||
sp_value: number;
|
||||
sp_max: number;
|
||||
enhanced_id?: number;
|
||||
}
|
||||
export interface MonsterJson {
|
||||
monster_id: number;
|
||||
@@ -49,7 +50,6 @@ export interface DynamicKeyJson {
|
||||
value: number;
|
||||
}
|
||||
|
||||
//BattleBuff
|
||||
export interface BattleBuffJson {
|
||||
level: number;
|
||||
id: number;
|
||||
|
||||
+1
-4
@@ -1,5 +1,6 @@
|
||||
// Generated by ts-to-zod
|
||||
import { z } from "zod";
|
||||
import { lightconeStoreSchema, relicStoreSchema } from "./mics.zod";
|
||||
|
||||
export const characterInfoCardTypeSchema = z.object({
|
||||
key: z.number(),
|
||||
@@ -18,10 +19,6 @@ export const characterInfoCardTypeSchema = z.object({
|
||||
}))
|
||||
});
|
||||
|
||||
const lightconeStoreSchema = z.any();
|
||||
|
||||
const relicStoreSchema = z.any();
|
||||
|
||||
export const avatarProfileCardTypeSchema = z.object({
|
||||
key: z.number(),
|
||||
profile_name: z.string(),
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ const flatPropSchema = z.object({
|
||||
|
||||
const relicFlatSchema = z.object({
|
||||
props: z.array(flatPropSchema),
|
||||
setName: z.string(),
|
||||
setName: z.union([z.string(), z.number()]),
|
||||
setID: z.number()
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ const skillTreeSchema = z.object({
|
||||
|
||||
const equipmentFlatSchema = z.object({
|
||||
props: z.array(flatPropSchema),
|
||||
name: z.string()
|
||||
name: z.union([z.string(), z.number()])
|
||||
});
|
||||
|
||||
const equipmentSchema = z.object({
|
||||
|
||||
@@ -5,5 +5,6 @@ export * from "./enka.zod"
|
||||
export * from "./card.zod"
|
||||
export * from "./extraData.zod"
|
||||
export * from "./showcase.zod"
|
||||
export * from "./persistedStore.zod"
|
||||
export * from "./srtools.zod"
|
||||
|
||||
|
||||
@@ -125,4 +125,22 @@ export const micsSchema = z.object({
|
||||
as_config: asConfigStoreSchema,
|
||||
ce_config: ceConfigStoreSchema,
|
||||
peak_config: peakConfigStoreSchema
|
||||
}).superRefine((data, ctx) => {
|
||||
for (const [avatarId, avatar] of Object.entries(data.avatars)) {
|
||||
if (avatar.profileList.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["avatars", avatarId, "profileList"],
|
||||
message: "profileList must contain at least one profile"
|
||||
});
|
||||
}
|
||||
|
||||
if (avatar.profileSelect < 0 || avatar.profileSelect >= avatar.profileList.length) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["avatars", avatarId, "profileSelect"],
|
||||
message: "profileSelect must point to an existing profile"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from "zod";
|
||||
import type { ChangelogItemType } from "@/types";
|
||||
|
||||
export const changelogItemSchema: z.ZodType<ChangelogItemType> = z.object({
|
||||
version: z.string(),
|
||||
date: z.string(),
|
||||
type: z.string(),
|
||||
items: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const localePersistedSchema = z.object({
|
||||
locale: z.string(),
|
||||
theme: z.string(),
|
||||
currentVersion: z.string(),
|
||||
changelog: z.array(changelogItemSchema),
|
||||
});
|
||||
|
||||
export const connectPersistedSchema = z.object({
|
||||
connectionType: z.string(),
|
||||
privateType: z.string(),
|
||||
serverUrl: z.string(),
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
@@ -40,7 +40,8 @@ export const avatarJsonSchema = z.object({
|
||||
promotion: z.number(),
|
||||
techniques: z.array(z.number()),
|
||||
sp_value: z.number(),
|
||||
sp_max: z.number()
|
||||
sp_max: z.number(),
|
||||
enhanced_id: z.number().optional()
|
||||
});
|
||||
|
||||
export const monsterJsonSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user