FIX: All bug in Custom Enemy, UPDATE: Add specical main skill, optimaze ui in custom enemy
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m39s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m39s
This commit is contained in:
333726
data/config_maze.json
333726
data/config_maze.json
File diff suppressed because it is too large
Load Diff
229269
data/monster.cn.json
Normal file
229269
data/monster.cn.json
Normal file
File diff suppressed because it is too large
Load Diff
229269
data/monster.en.json
Normal file
229269
data/monster.en.json
Normal file
File diff suppressed because it is too large
Load Diff
229269
data/monster.jp.json
Normal file
229269
data/monster.jp.json
Normal file
File diff suppressed because it is too large
Load Diff
229269
data/monster.kr.json
Normal file
229269
data/monster.kr.json
Normal file
File diff suppressed because it is too large
Load Diff
18
src/app/api/[locale]/monster/[id]/route.ts
Normal file
18
src/app/api/[locale]/monster/[id]/route.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { loadMonster } from '@/lib/loader'
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||||
|
) {
|
||||||
|
|
||||||
|
const { id, locale } = await params
|
||||||
|
const monsterData = await loadMonster([id], locale)
|
||||||
|
const monster = monsterData[id]
|
||||||
|
|
||||||
|
if (!monster) {
|
||||||
|
return NextResponse.json({ error: 'Monster info not found' }, { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(monster)
|
||||||
|
}
|
||||||
20
src/app/api/[locale]/monster/route.ts
Normal file
20
src/app/api/[locale]/monster/route.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { loadMonster } from "@/lib/loader";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const monsterIds = body.monsterIds as string[];
|
||||||
|
const { locale } = await params;
|
||||||
|
|
||||||
|
if (!Array.isArray(monsterIds) || monsterIds.some(id => typeof id !== 'string')) {
|
||||||
|
return NextResponse.json({ error: 'Invalid monsterIds' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const monsterData = await loadMonster(monsterIds, locale);
|
||||||
|
|
||||||
|
return NextResponse.json(monsterData);
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ error: 'Failed to load monster data' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ChevronUp,
|
ChevronUp,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Search,
|
Search,
|
||||||
|
CopyPlus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import useMazeStore from "@/stores/mazeStore";
|
import useMazeStore from "@/stores/mazeStore";
|
||||||
@@ -18,6 +19,7 @@ import Image from "next/image";
|
|||||||
import { MonsterBasic } from "@/types";
|
import { MonsterBasic } from "@/types";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { listCurrentLanguageApi } from "@/constant/constant";
|
||||||
|
|
||||||
|
|
||||||
export default function CeBar() {
|
export default function CeBar() {
|
||||||
@@ -25,7 +27,7 @@ export default function CeBar() {
|
|||||||
const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null);
|
const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null);
|
||||||
const { Stage } = useMazeStore()
|
const { Stage } = useMazeStore()
|
||||||
const { ce_config, setCeConfig } = useUserDataStore()
|
const { ce_config, setCeConfig } = useUserDataStore()
|
||||||
const { listMonster } = useMonsterStore()
|
const { mapMonsterInfo } = useMonsterStore()
|
||||||
const { locale } = useLocaleStore()
|
const { locale } = useLocaleStore()
|
||||||
const transI18n = useTranslations("DataPage")
|
const transI18n = useTranslations("DataPage")
|
||||||
const [showSearchStage, setShowSearchStage] = useState(false)
|
const [showSearchStage, setShowSearchStage] = useState(false)
|
||||||
@@ -34,9 +36,36 @@ export default function CeBar() {
|
|||||||
|
|
||||||
const pageSize = 30
|
const pageSize = 30
|
||||||
|
|
||||||
|
const pageSizeMonsters = 30
|
||||||
|
const [monsterPage, setMonsterPage] = useState(1)
|
||||||
|
|
||||||
|
const listMonsterDetail = useMemo(() => {
|
||||||
|
const result: MonsterBasic[] = []
|
||||||
|
|
||||||
|
for (const monster of Object.values(mapMonsterInfo)) {
|
||||||
|
for (const monsterChild of monster.Child) {
|
||||||
|
result.push({
|
||||||
|
id: monsterChild.Id.toString(),
|
||||||
|
rank: monster.Rank,
|
||||||
|
camp: null,
|
||||||
|
icon: monster.ImagePath,
|
||||||
|
weak: monsterChild.StanceWeakList,
|
||||||
|
desc: monster.Desc,
|
||||||
|
child: [],
|
||||||
|
lang: new Map<string, string>([
|
||||||
|
[listCurrentLanguageApi[locale], monster.Name],
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, [mapMonsterInfo, locale])
|
||||||
|
|
||||||
|
|
||||||
const filteredMonsters = useMemo(() => {
|
const filteredMonsters = useMemo(() => {
|
||||||
const newlistMonster = new Set<MonsterBasic>()
|
const newlistMonster = new Set<MonsterBasic>()
|
||||||
for (const monster of listMonster) {
|
for (const monster of listMonsterDetail) {
|
||||||
if (getLocaleName(locale, monster).toLowerCase().includes(searchTerm.toLowerCase())) {
|
if (getLocaleName(locale, monster).toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||||
newlistMonster.add(monster)
|
newlistMonster.add(monster)
|
||||||
}
|
}
|
||||||
@@ -45,7 +74,21 @@ export default function CeBar() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Array.from(newlistMonster)
|
return Array.from(newlistMonster)
|
||||||
}, [listMonster, locale, searchTerm]);
|
}, [listMonsterDetail, locale, searchTerm]);
|
||||||
|
|
||||||
|
const paginatedMonsters = useMemo(() =>
|
||||||
|
filteredMonsters.slice((monsterPage - 1) * pageSizeMonsters, monsterPage * pageSizeMonsters),
|
||||||
|
[filteredMonsters, monsterPage]
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasMoreMonsterPages = useMemo(
|
||||||
|
() => monsterPage * pageSizeMonsters < filteredMonsters.length,
|
||||||
|
[monsterPage, filteredMonsters]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMonsterPage(1) // reset về trang 1 khi searchTerm thay đổi
|
||||||
|
}, [searchTerm])
|
||||||
|
|
||||||
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
|
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
|
||||||
id: stage.stage_id.toString(),
|
id: stage.stage_id.toString(),
|
||||||
@@ -68,35 +111,52 @@ export default function CeBar() {
|
|||||||
}, [stageSearchTerm])
|
}, [stageSearchTerm])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6 z-4" onClick={() => {
|
<div className="p-6 z-4 h-full w-full" onClick={() => {
|
||||||
|
|
||||||
setShowSearchWaveId(null)
|
setShowSearchWaveId(null)
|
||||||
setShowSearchStage(false)
|
setShowSearchStage(false)
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
<div className="mb-4 w-full relative">
|
<div className="mb-4 w-full relative">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Search className="w-6 h-6" />
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-outline w-[95%] text-left"
|
className="btn btn-outline w-full text-left flex items-center gap-2"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setShowSearchStage(true)
|
setShowSearchStage(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Search className="w-6 h-6" />
|
||||||
<span className="text-left"> {transI18n("stage")}: {stageList.find((s) => s.id === ce_config.stage_id.toString())?.name || transI18n("selectStage")}</span>
|
<span className="text-left"> {transI18n("stage")}: {stageList.find((s) => s.id === ce_config.stage_id.toString())?.name || transI18n("selectStage")}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showSearchStage && (
|
{showSearchStage && (
|
||||||
<div className="absolute top-full mt-2 w-full z-50 border bg-base-200 border-slate-600 rounded-lg p-4 shadow-lg">
|
<div onClick={(e) => e.stopPropagation()} className="absolute top-full mt-2 w-full z-50 border bg-base-200 border-slate-600 rounded-lg p-4 shadow-lg">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Search className="w-4 h-4 text-slate-400" />
|
|
||||||
|
<label className="input w-full">
|
||||||
|
<svg className="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.3-4.3"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="search" className="grow"
|
||||||
placeholder={transI18n("searchStage")}
|
placeholder={transI18n("searchStage")}
|
||||||
className="input input-sm w-full placeholder-slate-400"
|
|
||||||
value={stageSearchTerm}
|
value={stageSearchTerm}
|
||||||
onChange={(e) => setStageSearchTerm(e.target.value)}
|
onChange={(e) => setStageSearchTerm(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-60 overflow-y-auto space-y-1">
|
<div className="max-h-60 overflow-y-auto space-y-1">
|
||||||
@@ -107,7 +167,9 @@ export default function CeBar() {
|
|||||||
key={stage.id}
|
key={stage.id}
|
||||||
className="p-2 hover:bg-primary/20 rounded cursor-pointer"
|
className="p-2 hover:bg-primary/20 rounded cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (ce_config.stage_id !== Number(stage.id)) {
|
||||||
setCeConfig({ ...ce_config, stage_id: Number(stage.id), cycle_count: 30 })
|
setCeConfig({ ...ce_config, stage_id: Number(stage.id), cycle_count: 30 })
|
||||||
|
}
|
||||||
setShowSearchStage(false)
|
setShowSearchStage(false)
|
||||||
setStageSearchTerm("")
|
setStageSearchTerm("")
|
||||||
}}
|
}}
|
||||||
@@ -191,6 +253,20 @@ export default function CeBar() {
|
|||||||
className="btn btn-sm btn-success">
|
className="btn btn-sm btn-success">
|
||||||
<ChevronDown className="w-4 h-4" />
|
<ChevronDown className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const newCeConfig = cloneDeep(ce_config)
|
||||||
|
const waves = newCeConfig.monsters
|
||||||
|
const temp = waves[waveIndex]
|
||||||
|
newCeConfig.monsters.push([...temp])
|
||||||
|
setCeConfig(newCeConfig)
|
||||||
|
}}
|
||||||
|
className="btn btn-sm btn-success">
|
||||||
|
<CopyPlus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -210,7 +286,22 @@ export default function CeBar() {
|
|||||||
<div className="card border hover:border-slate-500 transition-colors w-full h-full">
|
<div className="card border hover:border-slate-500 transition-colors w-full h-full">
|
||||||
<div className="card-body p-4">
|
<div className="card-body p-4">
|
||||||
<button
|
<button
|
||||||
className="btn btn-xs btn-error absolute -top-2 -right-2 opacity-50 group-hover:opacity-100 transition-opacity"
|
className="btn btn-xs btn-success absolute -top-2 right-5 opacity-50 group-hover:opacity-100 transition-opacity"
|
||||||
|
onClick={() => {
|
||||||
|
const newCeConfig = cloneDeep(ce_config)
|
||||||
|
|
||||||
|
newCeConfig.monsters[waveIndex].push({
|
||||||
|
monster_id: Number(member.monster_id),
|
||||||
|
level: member.level,
|
||||||
|
amount: member.amount,
|
||||||
|
})
|
||||||
|
setCeConfig(newCeConfig)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyPlus className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-error absolute -top-2 -right-4 opacity-50 group-hover:opacity-100 transition-opacity"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newCeConfig = cloneDeep(ce_config)
|
const newCeConfig = cloneDeep(ce_config)
|
||||||
newCeConfig.monsters[waveIndex].splice(memberIndex, 1)
|
newCeConfig.monsters[waveIndex].splice(memberIndex, 1)
|
||||||
@@ -220,10 +311,9 @@ export default function CeBar() {
|
|||||||
<Trash2 className="w-3 h-3" />
|
<Trash2 className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{listMonster.find((monster2) => monster2.child.includes(member.monster_id))?.icon && <Image
|
{listMonsterDetail.find((monster2) => monster2.id === member.monster_id.toString())?.icon && <Image
|
||||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster2) => monster2.child.includes(member.monster_id))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonsterDetail.find((monster2) => monster2.id === member.monster_id.toString())?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||||
alt="Enemy Icon"
|
alt="Enemy Icon"
|
||||||
width={376}
|
width={376}
|
||||||
height={512}
|
height={512}
|
||||||
@@ -231,9 +321,9 @@ export default function CeBar() {
|
|||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center gap-1 mb-2">
|
<div className="flex flex-wrap justify-center gap-1 mb-2">
|
||||||
{listMonster
|
{listMonsterDetail
|
||||||
.find((monster) => monster.child.includes(member.monster_id))
|
.find((monster) => monster.id === member.monster_id.toString())
|
||||||
?.weak?.map((icon, iconIndex) => (
|
?.weak?.map((icon, iconIndex) => (
|
||||||
<Image
|
<Image
|
||||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||||
@@ -247,7 +337,7 @@ export default function CeBar() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-center flex flex-col items-center justify-center">
|
<div className="text-center flex flex-col items-center justify-center">
|
||||||
<div className="text-sm font-medium">
|
<div className="text-sm font-medium">
|
||||||
{getLocaleName(locale, listMonster.find((monster) => monster.child.includes(member.monster_id)))}
|
{getLocaleName(locale, listMonsterDetail.find((monster) => monster.id === member.monster_id.toString())) } {`(${member.monster_id})`}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 mt-1">
|
<div className="flex items-center gap-1 mt-1">
|
||||||
<span className="text-sm">Lv.</span>
|
<span className="text-sm">Lv.</span>
|
||||||
@@ -257,14 +347,12 @@ export default function CeBar() {
|
|||||||
value={member.level}
|
value={member.level}
|
||||||
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
try {
|
const val = Number(e.target.value)
|
||||||
Number(e.target.value)
|
if (isNaN(val) || val < 1 || val > 95) return
|
||||||
} catch {
|
if (ce_config.monsters[waveIndex][memberIndex].level === val) return
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Number(e.target.value) < 1 || Number(e.target.value) > 95) return;
|
|
||||||
const newCeConfig = cloneDeep(ce_config)
|
const newCeConfig = cloneDeep(ce_config)
|
||||||
newCeConfig.monsters[waveIndex][memberIndex].level = Number(e.target.value)
|
newCeConfig.monsters[waveIndex][memberIndex].level = val
|
||||||
setCeConfig(newCeConfig)
|
setCeConfig(newCeConfig)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -277,11 +365,16 @@ export default function CeBar() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Add Member Button + Search */}
|
{/* Add Member Button + Search */}
|
||||||
<div className="relative flex items-start justify-center w-full h-full">
|
<div className="relative flex items-start justify-center w-full h-full z-39" onClick={(e) => e.stopPropagation()}>
|
||||||
<button
|
<button
|
||||||
className="btn btn-outline btn-primary w-full h-full border-dashed"
|
className="btn btn-outline btn-primary w-full h-full border-dashed"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (showSearchWaveId === waveIndex) {
|
||||||
|
setShowSearchWaveId(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setShowSearchWaveId(waveIndex)
|
setShowSearchWaveId(waveIndex)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -289,22 +382,37 @@ export default function CeBar() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showSearchWaveId === waveIndex && (
|
{showSearchWaveId === waveIndex && (
|
||||||
<div className="absolute top-full mt-2 w-72 z-50 border bg-base-200 border-slate-600 rounded-lg p-4 shadow-lg">
|
<div className="absolute top-full mt-2 w-72 border bg-base-200 border-slate-600 rounded-lg p-4 shadow-lg">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Search className="w-4 h-4" />
|
|
||||||
|
<label className="input w-full">
|
||||||
|
<svg className="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.3-4.3"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="search" className="grow"
|
||||||
placeholder={transI18n("searchMonster")}
|
placeholder={transI18n("searchMonster")}
|
||||||
className="input input-sm w-full placeholder-slate-400"
|
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-60 overflow-y-auto space-y-1">
|
<div className="max-h-60 overflow-y-auto space-y-1">
|
||||||
{filteredMonsters.length > 0 ? (
|
{paginatedMonsters.length > 0 ? (
|
||||||
filteredMonsters.map((monster) => (
|
paginatedMonsters.map((monster) => (
|
||||||
<div
|
<div
|
||||||
key={monster.id}
|
key={monster.id}
|
||||||
className="flex items-center gap-2 p-2 hover:bg-success/40 rounded cursor-pointer"
|
className="flex items-center gap-2 p-2 hover:bg-success/40 rounded cursor-pointer"
|
||||||
@@ -316,13 +424,13 @@ export default function CeBar() {
|
|||||||
amount: 1,
|
amount: 1,
|
||||||
})
|
})
|
||||||
setCeConfig(newCeConfig)
|
setCeConfig(newCeConfig)
|
||||||
setShowSearchWaveId(null);
|
setShowSearchWaveId(null)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative w-8 h-8 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
<div className="relative w-8 h-8 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||||
{listMonster.find((monster2) => monster2.child.includes(Number(monster.id)))?.icon?.split("/")?.pop()?.replace(".png", "") && (
|
{listMonsterDetail.find((monster2) => monster2.id === monster.id)?.icon?.split("/")?.pop()?.replace(".png", "") && (
|
||||||
<Image
|
<Image
|
||||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster2) => monster2.child.includes(Number(monster.id)))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonsterDetail.find((monster2) => monster2.id ===monster.id)?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||||
alt="Enemy Icon"
|
alt="Enemy Icon"
|
||||||
width={376}
|
width={376}
|
||||||
height={512}
|
height={512}
|
||||||
@@ -330,7 +438,7 @@ export default function CeBar() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="">{getLocaleName(locale, monster)} {`(${monster.id})`}</span>
|
<span>{getLocaleName(locale, monster)} {`(${monster.id})`}</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@@ -339,6 +447,33 @@ export default function CeBar() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{filteredMonsters.length > 0 && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
|
||||||
|
<button
|
||||||
|
disabled={monsterPage === 1}
|
||||||
|
className="btn btn-sm btn-outline btn-success"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setMonsterPage(monsterPage - 1)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{transI18n("previous")}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
disabled={!hasMoreMonsterPages}
|
||||||
|
className="btn btn-sm btn-outline btn-success"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setMonsterPage(monsterPage + 1)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{transI18n("next")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -348,7 +483,7 @@ export default function CeBar() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Add New Wave Button */}
|
{/* Add New Wave Button */}
|
||||||
<div className="card backdrop-blur-sm border border-slate-700/50 border-dashed">
|
<div className="card border border-slate-700/50 border-dashed">
|
||||||
<div className="card-body p-8">
|
<div className="card-body p-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -165,7 +165,11 @@ export default function SkillsInfo() {
|
|||||||
}}
|
}}
|
||||||
className={`w-full h-full object-cover rounded-xl`}
|
className={`w-full h-full object-cover rounded-xl`}
|
||||||
/>
|
/>
|
||||||
{traceButtons.map((btn, index) => (
|
{traceButtons.map((btn, index) => {
|
||||||
|
if (!avatarInfo?.SkillTrees?.[btn.id]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${btn.id} + ${index}`}
|
key={`${btn.id} + ${index}`}
|
||||||
id={btn.id}
|
id={btn.id}
|
||||||
@@ -206,12 +210,12 @@ export default function SkillsInfo() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{btn.size === "big" && (
|
{btn.size === "big" && (
|
||||||
<p className="text-xs md:text-base font-bold text-center rounded-full absolute bottom-[-1.4vw] left-1/2 transform -translate-x-1/2">{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}</p>
|
<p className="text-xs md:text-base font-bold text-center rounded-full absolute bottom-[-1.4vw] left-1/2 transform -translate-x-1/2">{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] || 0}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
))}
|
)})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ export const traceButtonsInfo: Record<string, { id: string, size: string, left:
|
|||||||
{ id: 'Point15', size: 'small', left: '65%', top: '82%' },
|
{ id: 'Point15', size: 'small', left: '65%', top: '82%' },
|
||||||
{ id: 'Point09', size: 'small', left: '9%', top: '51%' },
|
{ id: 'Point09', size: 'small', left: '9%', top: '51%' },
|
||||||
{ id: 'Point10', size: 'small', left: '13%', top: '40%' },
|
{ id: 'Point10', size: 'small', left: '13%', top: '40%' },
|
||||||
{ id: 'Point11', size: 'small', left: '13%', top: '63%' }
|
{ id: 'Point11', size: 'small', left: '13%', top: '63%' },
|
||||||
|
{ id: 'Point21', size: 'big', left: '70%', top: '34%'}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,6 +1009,14 @@ export const traceShowCaseMap : Record<string, Record<string, { id: string, size
|
|||||||
size: "big-memory",
|
size: "big-memory",
|
||||||
isLink: false
|
isLink: false
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"6": [
|
||||||
|
{
|
||||||
|
id: "Point21",
|
||||||
|
size: "big",
|
||||||
|
isLink: false
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { getMonsterDetailApi, getMonsterListApi } from '@/lib/api'
|
import { getMonsterValueApi, getMonsterListApi, fetchMonsterByIdsNative } from '@/lib/api'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import useMonsterStore from '@/stores/monsterStore'
|
import useMonsterStore from '@/stores/monsterStore'
|
||||||
|
import { listCurrentLanguageApi } from '@/constant/constant'
|
||||||
|
import useLocaleStore from '@/stores/localeStore'
|
||||||
|
|
||||||
export const useFetchMonsterData = () => {
|
export const useFetchMonsterData = () => {
|
||||||
const { setAllMapMonster, setListMonster, setAllMapMonsterInfo } = useMonsterStore()
|
const { setAllMapMonster, setListMonster, setAllMapMonsterValue, setAllMapMonsterInfo } = useMonsterStore()
|
||||||
|
const { locale } = useLocaleStore()
|
||||||
const { data: dataMonster, error: errorMonster } = useQuery({
|
const { data: dataMonster, error: errorMonster } = useQuery({
|
||||||
queryKey: ['monsterData'],
|
queryKey: ['monsterData'],
|
||||||
queryFn: getMonsterListApi,
|
queryFn: getMonsterListApi,
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
})
|
})
|
||||||
const { data: dataMonsterInfo, error: errorMonsterInfo } = useQuery({
|
|
||||||
queryKey: ['monsterInfoData'],
|
const { data: dataMonsterValue, error: errorMonsterValue } = useQuery({
|
||||||
queryFn: getMonsterDetailApi,
|
queryKey: ['monsterValueData'],
|
||||||
|
queryFn: getMonsterValueApi,
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { data: dataMonsterDetail, error: errorMonsterDetail } = useQuery({
|
||||||
|
queryKey: ['monsterDetailData', locale],
|
||||||
|
queryFn: () =>
|
||||||
|
fetchMonsterByIdsNative(
|
||||||
|
dataMonster!.list.map((item) => item.id),
|
||||||
|
listCurrentLanguageApi[locale.toLowerCase()]
|
||||||
|
),
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
enabled: !!dataMonster,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataMonster && !errorMonster) {
|
if (dataMonster && !errorMonster) {
|
||||||
setListMonster(dataMonster.list.sort((a, b) => Number(b.id) - Number(a.id)))
|
setListMonster(dataMonster.list.sort((a, b) => Number(b.id) - Number(a.id)))
|
||||||
@@ -28,10 +43,18 @@ export const useFetchMonsterData = () => {
|
|||||||
}, [dataMonster, errorMonster, setAllMapMonster, setListMonster])
|
}, [dataMonster, errorMonster, setAllMapMonster, setListMonster])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataMonsterInfo && !errorMonsterInfo) {
|
if (dataMonsterValue && !errorMonsterValue) {
|
||||||
setAllMapMonsterInfo(dataMonsterInfo)
|
setAllMapMonsterValue(dataMonsterValue)
|
||||||
} else if (errorMonsterInfo) {
|
} else if (errorMonsterValue) {
|
||||||
toast.error("Failed to load monster info data")
|
toast.error("Failed to load monster value data")
|
||||||
}
|
}
|
||||||
}, [dataMonsterInfo, errorMonsterInfo, setAllMapMonsterInfo])
|
}, [dataMonsterValue, errorMonsterValue, setAllMapMonsterValue])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataMonsterDetail && !errorMonsterDetail) {
|
||||||
|
setAllMapMonsterInfo(dataMonsterDetail)
|
||||||
|
} else if (errorMonsterDetail) {
|
||||||
|
toast.error("Failed to load monster detail data")
|
||||||
|
}
|
||||||
|
}, [dataMonsterDetail, errorMonsterDetail, setAllMapMonsterInfo])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import { AffixDetail, ASDetail, CharacterDetail, ConfigMaze, FreeSRJson, LightConeDetail, MocDetail, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
|
import { AffixDetail, ASDetail, CharacterDetail, ConfigMaze, FreeSRJson, LightConeDetail, MocDetail, MonsterDetail, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { pSResponseSchema } from "@/zod";
|
import { pSResponseSchema } from "@/zod";
|
||||||
|
|
||||||
@@ -196,7 +196,6 @@ export async function fetchMOCByIdNative(ids: string, locale: string): Promise<M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function fetchPeakByIdsNative(ids: string[], locale: string): Promise<Record<string, PeakDetail> | null> {
|
export async function fetchPeakByIdsNative(ids: string[], locale: string): Promise<Record<string, PeakDetail> | null> {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post<Record<string, PeakDetail>>(`/api/${locale}/peak`, { peakIds: ids });
|
const res = await axios.post<Record<string, PeakDetail>>(`/api/${locale}/peak`, { peakIds: ids });
|
||||||
@@ -217,6 +216,28 @@ export async function fetchPeakByIdNative(ids: string, locale: string): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchMonsterByIdsNative(ids: string[], locale: string): Promise<Record<string, MonsterDetail> | null> {
|
||||||
|
try {
|
||||||
|
const res = await axios.post<Record<string, MonsterDetail>>(`/api/${locale}/monster`, { monsterIds: ids });
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch monster:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMonsterByIdNative(ids: string, locale: string): Promise<MonsterDetail | null> {
|
||||||
|
try {
|
||||||
|
const res = await axios.get<MonsterDetail>(`/api/${locale}/monster/${ids}`);
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch monster:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function SendDataToServer(username: string, password: string, serverUrl: string, data: FreeSRJson | null): Promise<PSResponse | string> {
|
export async function SendDataToServer(username: string, password: string, serverUrl: string, data: FreeSRJson | null): Promise<PSResponse | string> {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`${serverUrl}`, { username, password, data })
|
const response = await axios.post(`${serverUrl}`, { username, password, data })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { convertAvatar, convertEvent, convertLightcone, convertMonster, convertRelicSet } from "@/helper";
|
import { convertAvatar, convertEvent, convertLightcone, convertMonster, convertRelicSet } from "@/helper";
|
||||||
import { ASDetail, CharacterBasic, CharacterBasicRaw, CharacterDetail, EventBasic, EventBasicRaw, LightConeBasic, LightConeBasicRaw, LightConeDetail, MocDetail, MonsterBasic, MonsterBasicRaw, MonsterDetail, PeakDetail, PFDetail, RelicBasic, RelicBasicRaw, RelicDetail } from "@/types";
|
import { ASDetail, CharacterBasic, CharacterBasicRaw, CharacterDetail, EventBasic, EventBasicRaw, LightConeBasic, LightConeBasicRaw, LightConeDetail, MocDetail, MonsterBasic, MonsterBasicRaw, MonsterDetail, MonsterValue, PeakDetail, PFDetail, RelicBasic, RelicBasicRaw, RelicDetail } from "@/types";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export async function getLightconeInfoApi(lightconeId: number, locale: string): Promise<LightConeDetail | null> {
|
export async function getLightconeInfoApi(lightconeId: number, locale: string): Promise<LightConeDetail | null> {
|
||||||
@@ -352,9 +352,9 @@ export async function getMonsterListApi(): Promise<{list: MonsterBasic[], map: R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMonsterDetailApi(): Promise<Record<string, MonsterDetail> | null> {
|
export async function getMonsterValueApi(): Promise<Record<string, MonsterValue> | null> {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<Record<string, MonsterDetail>>(
|
const res = await axios.get<Record<string, MonsterValue>>(
|
||||||
`https://api.hakush.in/hsr/data/monstervalue.json`,
|
`https://api.hakush.in/hsr/data/monstervalue.json`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -373,3 +373,27 @@ export async function getMonsterDetailApi(): Promise<Record<string, MonsterDetai
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function getMonsterDetailApi(monsterId: number, locale: string): Promise<MonsterDetail | null> {
|
||||||
|
try {
|
||||||
|
const res = await axios.get<MonsterDetail>(
|
||||||
|
`https://api.hakush.in/hsr/data/${locale}/monster/${monsterId}.json`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Unexpected error: ${String(error)}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,4 @@ export * from "./asLoader";
|
|||||||
export * from "./pfLoader";
|
export * from "./pfLoader";
|
||||||
export * from "./mocLoader";
|
export * from "./mocLoader";
|
||||||
export * from "./peakLoader";
|
export * from "./peakLoader";
|
||||||
|
export * from "./monsterLoader";
|
||||||
|
|||||||
51
src/lib/loader/monsterLoader.ts
Normal file
51
src/lib/loader/monsterLoader.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { MonsterDetail } from '@/types';
|
||||||
|
import { getMonsterDetailApi } from '../api';
|
||||||
|
|
||||||
|
const DATA_DIR = path.join(process.cwd(), 'data');
|
||||||
|
const monsterFileCache: Record<string, Record<string, MonsterDetail>> = {};
|
||||||
|
export let monsterMap: Record<string, MonsterDetail> = {};
|
||||||
|
|
||||||
|
function getJsonFilePath(locale: string): string {
|
||||||
|
return path.join(DATA_DIR, `monster.${locale}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromFileIfExists(locale: string): Record<string, MonsterDetail> | null {
|
||||||
|
if (monsterFileCache[locale]) return monsterFileCache[locale];
|
||||||
|
|
||||||
|
const filePath = getJsonFilePath(locale);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as Record<string, MonsterDetail>;
|
||||||
|
monsterFileCache[locale] = data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadMonster(charIds: string[], locale: string): Promise<Record<string, MonsterDetail>> {
|
||||||
|
const fileData = loadFromFileIfExists(locale);
|
||||||
|
const fileIds = fileData ? Object.keys(fileData) : [];
|
||||||
|
|
||||||
|
if (fileData && charIds.every(id => fileIds.includes(id))) {
|
||||||
|
monsterMap = fileData;
|
||||||
|
return monsterMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, MonsterDetail> = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
charIds.map(async id => {
|
||||||
|
const info = await getMonsterDetailApi(Number(id), locale);
|
||||||
|
if (info) result[id] = info;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||||
|
const filePath = getJsonFilePath(locale);
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(result, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
monsterFileCache[locale] = result;
|
||||||
|
monsterMap = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,27 +1,52 @@
|
|||||||
import { MonsterBasic, MonsterDetail } from '@/types';
|
import { MonsterBasic, MonsterDetail, MonsterValue } from '@/types'
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
|
||||||
interface MonsterState {
|
interface MonsterState {
|
||||||
listMonster: MonsterBasic[];
|
listMonster: MonsterBasic[]
|
||||||
mapMonster: Record<string, MonsterBasic>;
|
mapMonster: Record<string, MonsterBasic>
|
||||||
mapMonsterInfo: Record<string, MonsterDetail>;
|
mapMonsterInfo: Record<string, MonsterDetail>
|
||||||
setListMonster: (newListMonster: MonsterBasic[]) => void;
|
mapMonsterValue: Record<string, MonsterValue>
|
||||||
setMapMonsterInfo: (monsterId: string, newMonster: MonsterDetail) => void;
|
setListMonster: (newListMonster: MonsterBasic[]) => void
|
||||||
setAllMapMonsterInfo: (newMonster: Record<string, MonsterDetail>) => void;
|
setMapMonsterInfo: (monsterId: string, newMonster: MonsterDetail) => void
|
||||||
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => void;
|
setAllMapMonsterInfo: (newMonster: Record<string, MonsterDetail>) => void
|
||||||
setAllMapMonster: (newMonster: Record<string, MonsterBasic>) => void;
|
setMapMonsterValue: (monsterId: string, newMonster: MonsterValue) => void
|
||||||
|
setAllMapMonsterValue: (newMonster: Record<string, MonsterValue>) => void
|
||||||
|
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => void
|
||||||
|
setAllMapMonster: (newMonster: Record<string, MonsterBasic>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const useMonsterStore = create<MonsterState>((set) => ({
|
const useMonsterStore = create<MonsterState>((set) => ({
|
||||||
listMonster: [],
|
listMonster: [],
|
||||||
mapMonster: {},
|
mapMonster: {},
|
||||||
mapMonsterInfo: {},
|
mapMonsterInfo: {},
|
||||||
setListMonster: (newListMonster: MonsterBasic[]) => set({ listMonster: newListMonster }),
|
mapMonsterValue: {},
|
||||||
setMapMonster: (monsterId: string, newMonster: MonsterBasic) => set((state) => ({ mapMonster: { ...state.mapMonster, [monsterId]: newMonster } })),
|
|
||||||
setAllMapMonster: (newMonster: Record<string, MonsterBasic>) => set({ mapMonster: newMonster }),
|
|
||||||
setMapMonsterInfo: (monsterId: string, newMonster: MonsterDetail) => set((state) => ({ mapMonsterInfo: { ...state.mapMonsterInfo, [monsterId]: newMonster } })),
|
|
||||||
setAllMapMonsterInfo: (newMonster: Record<string, MonsterDetail>) => set({ mapMonsterInfo: newMonster }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default useMonsterStore;
|
setListMonster: (newListMonster) =>
|
||||||
|
set({ listMonster: newListMonster }),
|
||||||
|
|
||||||
|
setMapMonster: (monsterId, newMonster) =>
|
||||||
|
set((state) => ({
|
||||||
|
mapMonster: { ...state.mapMonster, [monsterId]: newMonster },
|
||||||
|
})),
|
||||||
|
|
||||||
|
setAllMapMonster: (newMonster) =>
|
||||||
|
set({ mapMonster: newMonster }),
|
||||||
|
|
||||||
|
setMapMonsterInfo: (monsterId, newMonster) =>
|
||||||
|
set((state) => ({
|
||||||
|
mapMonsterInfo: { ...state.mapMonsterInfo, [monsterId]: newMonster },
|
||||||
|
})),
|
||||||
|
|
||||||
|
setAllMapMonsterInfo: (newMonster) =>
|
||||||
|
set({ mapMonsterInfo: newMonster }),
|
||||||
|
|
||||||
|
setMapMonsterValue: (monsterId, newMonster) =>
|
||||||
|
set((state) => ({
|
||||||
|
mapMonsterValue: { ...state.mapMonsterValue, [monsterId]: newMonster },
|
||||||
|
})),
|
||||||
|
|
||||||
|
setAllMapMonsterValue: (newMonster) =>
|
||||||
|
set({ mapMonsterValue: newMonster }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default useMonsterStore
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ export * from "./monsterBasic"
|
|||||||
export * from "./pfDetail"
|
export * from "./pfDetail"
|
||||||
export * from "./asDetail"
|
export * from "./asDetail"
|
||||||
export * from "./mocDetail"
|
export * from "./mocDetail"
|
||||||
export * from "./monsterDetail"
|
export * from "./monsterValue"
|
||||||
export * from "./peakDetail"
|
export * from "./peakDetail"
|
||||||
|
export * from "./monsterDetail"
|
||||||
|
|||||||
@@ -1,4 +1,25 @@
|
|||||||
export interface MonsterChild {
|
export interface MonsterDetail {
|
||||||
|
Id: number
|
||||||
|
Name: string
|
||||||
|
Desc: string
|
||||||
|
MonsterCampID: number | null
|
||||||
|
AttackBase: number
|
||||||
|
CriticalDamageBase: number
|
||||||
|
DefenceBase: number
|
||||||
|
HPBase: number
|
||||||
|
InitialDelayRatio: number
|
||||||
|
ImagePath: string
|
||||||
|
MinimumFatigueRatio: number
|
||||||
|
Rank: string
|
||||||
|
SpeedBase: number
|
||||||
|
StanceBase: number
|
||||||
|
StanceCount: number
|
||||||
|
StatusResistanceBase: number
|
||||||
|
Child: MonsterDetailChild[]
|
||||||
|
Drop: MonsterDetailDrop[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonsterDetailChild {
|
||||||
Id: number
|
Id: number
|
||||||
AttackModifyRatio: number
|
AttackModifyRatio: number
|
||||||
DefenceModifyRatio: number
|
DefenceModifyRatio: number
|
||||||
@@ -7,17 +28,34 @@ export interface MonsterChild {
|
|||||||
SpeedModifyRatio: number
|
SpeedModifyRatio: number
|
||||||
SpeedModifyValue: number | null
|
SpeedModifyValue: number | null
|
||||||
StanceModifyRatio: number
|
StanceModifyRatio: number
|
||||||
HardLevelGroup: number
|
|
||||||
StanceWeakList: string[]
|
StanceWeakList: string[]
|
||||||
|
HardLevelGroup: number
|
||||||
|
DamageTypeResistance: MonsterDetailElementResistance[]
|
||||||
|
SkillList: MonsterDetailSkill[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonsterDetail {
|
export interface MonsterDetailElementResistance {
|
||||||
Rank: string
|
$type: string
|
||||||
AttackBase: number
|
DamageType: string
|
||||||
DefenceBase: number
|
Value: number
|
||||||
HPBase: number
|
}
|
||||||
SpeedBase: number
|
|
||||||
StanceBase: number
|
export interface MonsterDetailSkill {
|
||||||
StatusResistanceBase: number
|
Id: number
|
||||||
child: MonsterChild[]
|
SkillName: string | null
|
||||||
|
SkillDesc: string | null
|
||||||
|
DamageType: string
|
||||||
|
SPHitBase: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonsterDetailDrop {
|
||||||
|
MonsterTemplateID: number
|
||||||
|
WorldLevel?: number
|
||||||
|
AvatarExpReward: number
|
||||||
|
DisplayItemList: MonsterDetailDropItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonsterDetailDropItem {
|
||||||
|
$type: string
|
||||||
|
ID: number
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/types/monsterValue.ts
Normal file
23
src/types/monsterValue.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export interface MonsterChild {
|
||||||
|
Id: number
|
||||||
|
AttackModifyRatio: number
|
||||||
|
DefenceModifyRatio: number
|
||||||
|
EliteGroup: number
|
||||||
|
HPModifyRatio: number
|
||||||
|
SpeedModifyRatio: number
|
||||||
|
SpeedModifyValue: number | null
|
||||||
|
StanceModifyRatio: number
|
||||||
|
HardLevelGroup: number
|
||||||
|
StanceWeakList: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonsterValue {
|
||||||
|
Rank: string
|
||||||
|
AttackBase: number
|
||||||
|
DefenceBase: number
|
||||||
|
HPBase: number
|
||||||
|
SpeedBase: number
|
||||||
|
StanceBase: number
|
||||||
|
StatusResistanceBase: number
|
||||||
|
child: MonsterChild[]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user