add new content end game
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 2m2s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 2m2s
This commit is contained in:
@@ -5,7 +5,17 @@ import CharacterCard from "../card/characterCard"
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useFetchASData, useFetchAvatarData, useFetchConfigData, useFetchLightconeData, useFetchMOCData, useFetchMonsterData, useFetchPFData, useFetchRelicData } from "@/hooks"
|
||||
import {
|
||||
useFetchASData,
|
||||
useFetchAvatarData,
|
||||
useFetchConfigData,
|
||||
useFetchLightconeData,
|
||||
useFetchMOCData,
|
||||
useFetchMonsterData,
|
||||
useFetchPEAKData,
|
||||
useFetchPFData,
|
||||
useFetchRelicData
|
||||
} from "@/hooks"
|
||||
|
||||
|
||||
export default function AvatarBar() {
|
||||
@@ -23,6 +33,7 @@ export default function AvatarBar() {
|
||||
useFetchPFData()
|
||||
useFetchMOCData()
|
||||
useFetchASData()
|
||||
useFetchPEAKData()
|
||||
|
||||
useEffect(() => {
|
||||
setFilter({ ...filter, locale: locale, element: Object.keys(listElement).filter((key) => listElement[key]), path: Object.keys(listPath).filter((key) => listPath[key]) })
|
||||
|
||||
@@ -174,6 +174,7 @@ export default function AvatarInfo() {
|
||||
<button
|
||||
className="btn btn-sm btn-outline btn-warning"
|
||||
onClick={() => {
|
||||
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return
|
||||
const newSpValue = Math.ceil(avatars[avatarSelected?.id || ""]?.sp_max / 2);
|
||||
const newAvatar = { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue }
|
||||
setAvatar(newAvatar)
|
||||
|
||||
@@ -39,10 +39,12 @@ export default function Header() {
|
||||
pf_config,
|
||||
as_config,
|
||||
ce_config,
|
||||
peak_config,
|
||||
setMocConfig,
|
||||
setPfConfig,
|
||||
setAsConfig,
|
||||
setCeConfig,
|
||||
setPeakConfig,
|
||||
} = useUserDataStore()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -170,8 +172,10 @@ export default function Header() {
|
||||
setPfConfig(parsed.pf_config)
|
||||
setAsConfig(parsed.as_config)
|
||||
setCeConfig(parsed.ce_config)
|
||||
setPeakConfig(parsed.peak_config)
|
||||
toast.success(transI18n("importDatabaseSuccess"))
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
toast.error(transI18n("fileMustBeAValidJsonFile"))
|
||||
}
|
||||
};
|
||||
@@ -231,8 +235,26 @@ export default function Header() {
|
||||
<details>
|
||||
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium">{transI18n("exportData")}</summary>
|
||||
<ul className="p-2">
|
||||
<li><a onClick={() => downloadJson("freesr-data", converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config))}>{transI18n("freeSr")}</a></li>
|
||||
<li><a onClick={() => downloadJson("database-data", { avatars: avatars, battle_type: battle_type, moc_config: moc_config, pf_config: pf_config, as_config: as_config, ce_config: ce_config })}>{transI18n("database")}</a></li>
|
||||
<li><a onClick={() => downloadJson("freesr-data",
|
||||
converterToFreeSRJson(
|
||||
avatars,
|
||||
battle_type,
|
||||
moc_config,
|
||||
pf_config,
|
||||
as_config,
|
||||
ce_config,
|
||||
peak_config
|
||||
)
|
||||
)}>{transI18n("freeSr")}</a></li>
|
||||
<li><a onClick={() => downloadJson("database-data", {
|
||||
avatars: avatars,
|
||||
battle_type: battle_type,
|
||||
moc_config: moc_config,
|
||||
pf_config: pf_config,
|
||||
as_config: as_config,
|
||||
ce_config: ce_config,
|
||||
peak_config: peak_config
|
||||
})}>{transI18n("database")}</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
@@ -320,8 +342,26 @@ export default function Header() {
|
||||
<details>
|
||||
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium">{transI18n("exportData")}</summary>
|
||||
<ul className="p-2">
|
||||
<li><a onClick={() => downloadJson("freesr-data", converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config))}>{transI18n("freeSr")}</a></li>
|
||||
<li><a onClick={() => downloadJson("database-data", { avatars: avatars, battle_type: battle_type, moc_config: moc_config, pf_config: pf_config, as_config: as_config, ce_config: ce_config })}>{transI18n("database")}</a></li>
|
||||
<li><a onClick={() => downloadJson("freesr-data",
|
||||
converterToFreeSRJson(
|
||||
avatars,
|
||||
battle_type,
|
||||
moc_config,
|
||||
pf_config,
|
||||
as_config,
|
||||
ce_config,
|
||||
peak_config
|
||||
)
|
||||
)}>{transI18n("freeSr")}</a></li>
|
||||
<li><a onClick={() => downloadJson("database-data", {
|
||||
avatars: avatars,
|
||||
battle_type: battle_type,
|
||||
moc_config: moc_config,
|
||||
pf_config: pf_config,
|
||||
as_config: as_config,
|
||||
ce_config: ce_config,
|
||||
peak_config: peak_config
|
||||
})}>{transI18n("database")}</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function AsBar() {
|
||||
const challenge = mapASInfo[as_config.event_id.toString()]?.Level.find((as) => as.Id === as_config.challenge_id)
|
||||
if (as_config.event_id !== 0 && as_config.challenge_id !== 0 && challenge) {
|
||||
const newBattleConfig = cloneDeep(as_config)
|
||||
newBattleConfig.cycle_count = 4
|
||||
newBattleConfig.cycle_count = 0
|
||||
|
||||
newBattleConfig.blessings = []
|
||||
if (as_config.buff_id !== 0) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Image from "next/image";
|
||||
import AsBar from "./as";
|
||||
import { useTranslations } from "next-intl";
|
||||
import CeBar from "./ce";
|
||||
import PeakBar from "./peak";
|
||||
|
||||
export default function MonsterBar() {
|
||||
const { battle_type, setBattleType } = useUserDataStore()
|
||||
@@ -14,6 +15,7 @@ export default function MonsterBar() {
|
||||
{ name: transI18n("memoryOfChaos"), icon: 'AbyssIcon01', value: 'MOC' },
|
||||
{ name: transI18n("pureFiction"), icon: 'ChallengeStory', value: 'PF' },
|
||||
{ name: transI18n("apocalypticShadow"), icon: 'ChallengeBoss', value: 'AS' },
|
||||
{ name: transI18n("anomalyArbitration"), icon: 'AbyssIcon02', value: 'PEAK' },
|
||||
{ name: transI18n("customEnemy"), icon: 'MonsterIcon', value: 'CE' },
|
||||
{ name: transI18n("simulatedUniverse"), icon: 'SimulatedUniverse', value: 'SU' },
|
||||
];
|
||||
@@ -24,9 +26,9 @@ export default function MonsterBar() {
|
||||
{/* Header Navigation */}
|
||||
<nav className="border-b border-warning/30 relative">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-center h-16">
|
||||
<div className="flex items-center justify-center">
|
||||
{/* Navigation Tabs */}
|
||||
<div className="flex space-x-1">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-1">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
key={item.name}
|
||||
@@ -76,6 +78,7 @@ export default function MonsterBar() {
|
||||
{battle_type.toUpperCase() === 'PF' && <PfBar />}
|
||||
{battle_type.toUpperCase() === 'AS' && <AsBar />}
|
||||
{battle_type.toUpperCase() === 'CE' && <CeBar />}
|
||||
{battle_type.toUpperCase() === 'PEAK' && <PeakBar />}
|
||||
{battle_type.toUpperCase() === 'SU' && (
|
||||
<div className="container mx-auto px-4 py-8 text-center font-bold text-3xl">
|
||||
{transI18n("comingSoon")}
|
||||
|
||||
272
src/components/monsterBar/peak.tsx
Normal file
272
src/components/monsterBar/peak.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
"use client"
|
||||
import { useEffect, useMemo } from "react";
|
||||
import SelectCustomText from "../select/customSelectText";
|
||||
import useEventStore from "@/stores/eventStore";
|
||||
import { getLocaleName, replaceByParam } from "@/helper";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import useMonsterStore from "@/stores/monsterStore";
|
||||
import Image from "next/image";
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { MonsterStore } from "@/types";
|
||||
|
||||
export default function PeakBar() {
|
||||
const { PEAKEvent, mapPEAKInfo } = useEventStore()
|
||||
const { listMonster } = useMonsterStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
peak_config,
|
||||
setPeakConfig
|
||||
} = useUserDataStore()
|
||||
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
const listFloor = useMemo(() => {
|
||||
if (!mapPEAKInfo?.[peak_config?.event_id?.toString()]) return []
|
||||
return [
|
||||
...mapPEAKInfo[peak_config?.event_id?.toString()]?.PreLevel,
|
||||
mapPEAKInfo[peak_config?.event_id?.toString()]?.BossLevel,
|
||||
]
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
|
||||
const eventSelected = useMemo(() => {
|
||||
return mapPEAKInfo?.[peak_config?.event_id?.toString()]
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
|
||||
const bossConfig = useMemo(() => {
|
||||
return mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossConfig;
|
||||
}, [peak_config, mapPEAKInfo])
|
||||
|
||||
const challengeSelected = useMemo(() => {
|
||||
const challenge = cloneDeep(listFloor.find((peak) => peak.Id === peak_config.challenge_id))
|
||||
if (
|
||||
challenge
|
||||
&& challenge.Id === mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossLevel?.Id
|
||||
&& bossConfig
|
||||
&& peak_config?.boss_mode === "Hard"
|
||||
) {
|
||||
challenge.Name = bossConfig.HardName
|
||||
challenge.EventIDList = bossConfig.EventIDList
|
||||
challenge.InfiniteList = bossConfig.InfiniteList
|
||||
challenge.TagList = bossConfig.TagList
|
||||
}
|
||||
return challenge
|
||||
}, [peak_config, listFloor, mapPEAKInfo, bossConfig])
|
||||
|
||||
useEffect(() => {
|
||||
if (!challengeSelected) return
|
||||
if (peak_config.event_id !== 0 && peak_config.challenge_id !== 0 && challengeSelected) {
|
||||
const newBattleConfig = cloneDeep(peak_config)
|
||||
newBattleConfig.cycle_count = 6
|
||||
newBattleConfig.blessings = []
|
||||
for (const value of challengeSelected.TagList) {
|
||||
newBattleConfig.blessings.push({
|
||||
id: Number(value.Id),
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
if (peak_config.buff_id !== 0) {
|
||||
newBattleConfig.blessings.push({
|
||||
id: peak_config.buff_id,
|
||||
level: 1
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters = []
|
||||
newBattleConfig.stage_id = challengeSelected.EventIDList[0].StageID
|
||||
for (const wave of challengeSelected.EventIDList[0].MonsterList) {
|
||||
if (!wave) continue
|
||||
const newWave: MonsterStore[] = []
|
||||
for (const value of Object.values(wave)) {
|
||||
if (!value) continue
|
||||
newWave.push({
|
||||
monster_id: Number(value),
|
||||
level: challengeSelected.EventIDList[0].Level,
|
||||
amount: 1,
|
||||
})
|
||||
}
|
||||
newBattleConfig.monsters.push(newWave)
|
||||
}
|
||||
|
||||
setPeakConfig(newBattleConfig)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
peak_config.event_id,
|
||||
peak_config.challenge_id,
|
||||
peak_config.buff_id,
|
||||
mapPEAKInfo,
|
||||
])
|
||||
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 relative">
|
||||
|
||||
{/* Title Card */}
|
||||
<div className="rounded-xl p-4 mb-2 border border-warning">
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={PEAKEvent.filter(peak => peak.lang.get(locale)).map((peak) => ({
|
||||
id: peak.id,
|
||||
name: `${getLocaleName(locale, peak)} (${peak.id}) `,
|
||||
}))}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={peak_config.event_id.toString()}
|
||||
placeholder={transI18n("selectASEvent")}
|
||||
setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, event_id: Number(id), challenge_id: 0, buff_id: 0 })}
|
||||
/>
|
||||
</div>
|
||||
{/* Settings */}
|
||||
<div className={
|
||||
`grid grid-cols-1
|
||||
${eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id ? "md:grid-cols-2" : ""}
|
||||
gap-4 mb-4 justify-items-center items-center w-full`}
|
||||
>
|
||||
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span>
|
||||
</label>
|
||||
<select
|
||||
value={peak_config.challenge_id}
|
||||
className="select select-success w-full"
|
||||
onChange={(e) => setPeakConfig({ ...peak_config, challenge_id: Number(e.target.value) })}
|
||||
>
|
||||
<option value={0} disabled={true}>{transI18n("selectFloor")}</option>
|
||||
{listFloor.map((peak) => (
|
||||
<option key={peak.Id} value={peak.Id}>{peak.Name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id && (
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span>
|
||||
</label>
|
||||
<select
|
||||
value={peak_config.boss_mode}
|
||||
className="select select-success w-full"
|
||||
onChange={(e) => setPeakConfig({ ...peak_config, boss_mode: e.target.value })}
|
||||
>
|
||||
<option value={0} disabled={true}>{transI18n("selectSide")}</option>
|
||||
<option value="Normal">{transI18n("normalMode")}</option>
|
||||
<option value="Hard">{transI18n("hardMode")}</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{
|
||||
eventSelected
|
||||
&& eventSelected.BossLevel.Id === peak_config.challenge_id
|
||||
&& bossConfig
|
||||
&& bossConfig.BuffList
|
||||
&& (
|
||||
<div className="mb-4 w-full">
|
||||
<SelectCustomText
|
||||
customSet={
|
||||
Array.isArray(bossConfig.BuffList)
|
||||
? bossConfig.BuffList.map((buff) => ({
|
||||
id: buff.Id.toString(),
|
||||
name: buff?.Name || "",
|
||||
description: replaceByParam(buff?.Desc || "", buff?.Param || []),
|
||||
}))
|
||||
: []
|
||||
}
|
||||
excludeSet={[]}
|
||||
selectedCustomSet={peak_config?.buff_id?.toString()}
|
||||
placeholder={transI18n("selectBuff")}
|
||||
setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, buff_id: Number(id) })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Turbulence Buff */}
|
||||
|
||||
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
|
||||
<h2 className="text-2xl font-bold mb-2 text-info">
|
||||
{transI18n("turbulenceBuff")}
|
||||
</h2>
|
||||
|
||||
{challengeSelected && challengeSelected?.TagList?.length > 0 ? (
|
||||
challengeSelected.TagList.map((subOption, index) => (
|
||||
<div key={index}>
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-success">
|
||||
{index + 1}. {subOption.Name}
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceByParam(
|
||||
subOption.Desc,
|
||||
subOption.Param || []
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-base">{transI18n("noTurbulenceBuff")}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enemy Waves */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
|
||||
<div className="rounded-xl p-4 mt-2 border border-warning">
|
||||
<h2 className="text-2xl font-bold mb-6 text-info">{challengeSelected?.Name}</h2>
|
||||
|
||||
{challengeSelected && Object.values(challengeSelected.InfiniteList).map((waveValue, waveIndex) => (
|
||||
<div key={waveIndex} className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => (
|
||||
<div
|
||||
key={enemyIndex}
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList[0].Level}</div>
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
{listMonster
|
||||
.find((monster) => monster.child.includes(monsterId))
|
||||
?.weak?.map((icon, iconIndex) => (
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user