feat: implement combat simulation modules and localization support for game modes
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 44s

This commit is contained in:
2026-05-06 15:57:36 +07:00
parent dc15340cdf
commit d7e9501f22
27 changed files with 536 additions and 654 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Turbulenz-Buff verwenden?", "useTurbulenceBuff": "Turbulenz-Buff verwenden?",
"firstHalfEnemies": "Gegner erste Hälfte", "firstHalfEnemies": "Gegner erste Hälfte",
"secondHalfEnemies": "Gegner zweite Hälfte", "secondHalfEnemies": "Gegner zweite Hälfte",
"firstNodeEnemies": "Gegner Knoten 1",
"secondNodeEnemies": "Gegner Knoten 2",
"thirdNodeEnemies": "Gegner Knoten 3",
"firstNode": "Knoten 1",
"secondNode": "Knoten 2",
"thirdNode": "Knoten 3",
"listEnemies": "Gegnerliste", "listEnemies": "Gegnerliste",
"turbulenceBuff": "Turbulenz-Buff", "turbulenceBuff": "Turbulenz-Buff",
"noEventSelected": "Kein Ereignis ausgewählt", "noEventSelected": "Kein Ereignis ausgewählt",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Use turbulence buff?", "useTurbulenceBuff": "Use turbulence buff?",
"firstHalfEnemies": "First half enemies", "firstHalfEnemies": "First half enemies",
"secondHalfEnemies": "Second half enemies", "secondHalfEnemies": "Second half enemies",
"firstNodeEnemies": "First node enemies",
"secondNodeEnemies": "Second node enemies",
"thirdNodeEnemies": "Third node enemies",
"firstNode": "First Node",
"secondNode": "Second Node",
"thirdNode": "Third Node",
"listEnemies": "List enemies", "listEnemies": "List enemies",
"turbulenceBuff": "Turbulence Buff", "turbulenceBuff": "Turbulence Buff",
"noEventSelected": "No event selected", "noEventSelected": "No event selected",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "¿Usar buff de turbulencia?", "useTurbulenceBuff": "¿Usar buff de turbulencia?",
"firstHalfEnemies": "Enemigos primera mitad", "firstHalfEnemies": "Enemigos primera mitad",
"secondHalfEnemies": "Enemigos segunda mitad", "secondHalfEnemies": "Enemigos segunda mitad",
"firstNodeEnemies": "Enemigos del nodo 1",
"secondNodeEnemies": "Enemigos del nodo 2",
"thirdNodeEnemies": "Enemigos del nodo 3",
"firstNode": "Nodo 1",
"secondNode": "Nodo 2",
"thirdNode": "Nodo 3",
"listEnemies": "Lista de enemigos", "listEnemies": "Lista de enemigos",
"turbulenceBuff": "Buff de Turbulencia", "turbulenceBuff": "Buff de Turbulencia",
"noEventSelected": "Ningún evento seleccionado", "noEventSelected": "Ningún evento seleccionado",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Utiliser le buff de turbulence ?", "useTurbulenceBuff": "Utiliser le buff de turbulence ?",
"firstHalfEnemies": "Ennemis première moitié", "firstHalfEnemies": "Ennemis première moitié",
"secondHalfEnemies": "Ennemis deuxième moitié", "secondHalfEnemies": "Ennemis deuxième moitié",
"firstNodeEnemies": "Ennemis du nœud 1",
"secondNodeEnemies": "Ennemis du nœud 2",
"thirdNodeEnemies": "Ennemis du nœud 3",
"firstNode": "Nœud 1",
"secondNode": "Nœud 2",
"thirdNode": "Nœud 3",
"listEnemies": "Liste des ennemis", "listEnemies": "Liste des ennemis",
"turbulenceBuff": "Buff de Turbulence", "turbulenceBuff": "Buff de Turbulence",
"noEventSelected": "Aucun événement sélectionné", "noEventSelected": "Aucun événement sélectionné",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Gunakan buff turbulence?", "useTurbulenceBuff": "Gunakan buff turbulence?",
"firstHalfEnemies": "Musuh paruh pertama", "firstHalfEnemies": "Musuh paruh pertama",
"secondHalfEnemies": "Musuh paruh kedua", "secondHalfEnemies": "Musuh paruh kedua",
"firstNodeEnemies": "Musuh Node 1",
"secondNodeEnemies": "Musuh Node 2",
"thirdNodeEnemies": "Musuh Node 3",
"firstNode": "Node 1",
"secondNode": "Node 2",
"thirdNode": "Node 3",
"listEnemies": "Daftar musuh", "listEnemies": "Daftar musuh",
"turbulenceBuff": "Turbulence Buff", "turbulenceBuff": "Turbulence Buff",
"noEventSelected": "Tidak ada event dipilih", "noEventSelected": "Tidak ada event dipilih",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "乱気流バフを使用しますか?", "useTurbulenceBuff": "乱気流バフを使用しますか?",
"firstHalfEnemies": "前半の敵", "firstHalfEnemies": "前半の敵",
"secondHalfEnemies": "後半の敵", "secondHalfEnemies": "後半の敵",
"firstNodeEnemies": "ノード 1 の敵",
"secondNodeEnemies": "ノード 2 の敵",
"thirdNodeEnemies": "ノード 3 の敵",
"firstNode": "ノード 1",
"secondNode": "ノード 2",
"thirdNode": "ノード 3",
"turbulenceBuff": "乱気流バフ", "turbulenceBuff": "乱気流バフ",
"noEventSelected": "イベントが選択されていません", "noEventSelected": "イベントが選択されていません",
"noTurbulenceBuff": "乱気流バフがありません", "noTurbulenceBuff": "乱気流バフがありません",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "난류 버프 사용?", "useTurbulenceBuff": "난류 버프 사용?",
"firstHalfEnemies": "전반 적", "firstHalfEnemies": "전반 적",
"secondHalfEnemies": "후반 적", "secondHalfEnemies": "후반 적",
"firstNodeEnemies": "노드 1 적",
"secondNodeEnemies": "노드 2 적",
"thirdNodeEnemies": "노드 3 적",
"firstNode": "노드 1",
"secondNode": "노드 2",
"thirdNode": "노드 3",
"turbulenceBuff": "난류 버프", "turbulenceBuff": "난류 버프",
"noEventSelected": "이벤트가 선택되지 않음", "noEventSelected": "이벤트가 선택되지 않음",
"noTurbulenceBuff": "난류 버프가 없음", "noTurbulenceBuff": "난류 버프가 없음",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Usar buff de turbulência?", "useTurbulenceBuff": "Usar buff de turbulência?",
"firstHalfEnemies": "Inimigos da primeira metade", "firstHalfEnemies": "Inimigos da primeira metade",
"secondHalfEnemies": "Inimigos da segunda metade", "secondHalfEnemies": "Inimigos da segunda metade",
"firstNodeEnemies": "Inimigos do nodo 1",
"secondNodeEnemies": "Inimigos do nodo 2",
"thirdNodeEnemies": "Inimigos do nodo 3",
"firstNode": "Nodo 1",
"secondNode": "Nodo 2",
"thirdNode": "Nodo 3",
"listEnemies": "Lista de inimigos", "listEnemies": "Lista de inimigos",
"turbulenceBuff": "Buff de Turbulência", "turbulenceBuff": "Buff de Turbulência",
"noEventSelected": "Nenhum evento selecionado", "noEventSelected": "Nenhum evento selecionado",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Использовать бафф турбулентности?", "useTurbulenceBuff": "Использовать бафф турбулентности?",
"firstHalfEnemies": "Враги первой половины", "firstHalfEnemies": "Враги первой половины",
"secondHalfEnemies": "Враги второй половины", "secondHalfEnemies": "Враги второй половины",
"firstNodeEnemies": "Враги узла 1",
"secondNodeEnemies": "Враги узла 2",
"thirdNodeEnemies": "Враги узла 3",
"firstNode": "Узел 1",
"secondNode": "Узел 2",
"thirdNode": "Узел 3",
"listEnemies": "Список врагов", "listEnemies": "Список врагов",
"turbulenceBuff": "Бафф турбулентности", "turbulenceBuff": "Бафф турбулентности",
"noEventSelected": "Событие не выбрано", "noEventSelected": "Событие не выбрано",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "ใช้บัฟบรรยากาศหรือไม่?", "useTurbulenceBuff": "ใช้บัฟบรรยากาศหรือไม่?",
"firstHalfEnemies": "ศัตรูครึ่งแรก", "firstHalfEnemies": "ศัตรูครึ่งแรก",
"secondHalfEnemies": "ศัตรูครึ่งหลัง", "secondHalfEnemies": "ศัตรูครึ่งหลัง",
"firstNodeEnemies": "ศัตรูโหนด 1",
"secondNodeEnemies": "ศัตรูโหนด 2",
"thirdNodeEnemies": "ศัตรูโหนด 3",
"firstNode": "โหนด 1",
"secondNode": "โหนด 2",
"thirdNode": "โหนด 3",
"listEnemies": "รายการศัตรู", "listEnemies": "รายการศัตรู",
"turbulenceBuff": "บัฟบรรยากาศ", "turbulenceBuff": "บัฟบรรยากาศ",
"noEventSelected": "ไม่ได้เลือกอีเวนต์", "noEventSelected": "ไม่ได้เลือกอีเวนต์",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "Dùng buff hỗn loạn?", "useTurbulenceBuff": "Dùng buff hỗn loạn?",
"firstHalfEnemies": "Địch nửa đầu", "firstHalfEnemies": "Địch nửa đầu",
"secondHalfEnemies": "Địch nửa sau", "secondHalfEnemies": "Địch nửa sau",
"firstNodeEnemies": "Địch Node 1",
"secondNodeEnemies": "Địch Node 2",
"thirdNodeEnemies": "Địch Node 3",
"firstNode": "Node 1",
"secondNode": "Node 2",
"thirdNode": "Node 3",
"turbulenceBuff": "Buff hỗn loạn", "turbulenceBuff": "Buff hỗn loạn",
"noEventSelected": "Không có sự kiện", "noEventSelected": "Không có sự kiện",
"noTurbulenceBuff": "Không có buff hỗn loạn", "noTurbulenceBuff": "Không có buff hỗn loạn",

View File

@@ -189,6 +189,12 @@
"useTurbulenceBuff": "使用记忆紊流?", "useTurbulenceBuff": "使用记忆紊流?",
"firstHalfEnemies": "上半场敌人", "firstHalfEnemies": "上半场敌人",
"secondHalfEnemies": "下半场敌人", "secondHalfEnemies": "下半场敌人",
"firstNodeEnemies": "节点 1 敌人",
"secondNodeEnemies": "节点 2 敌人",
"thirdNodeEnemies": "节点 3 敌人",
"firstNode": "节点 1",
"secondNode": "节点 2",
"thirdNode": "节点 3",
"turbulenceBuff": "增益效果", "turbulenceBuff": "增益效果",
"noEventSelected": "未选择事件", "noEventSelected": "未选择事件",
"noTurbulenceBuff": "未选择增益效果", "noTurbulenceBuff": "未选择增益效果",

View File

@@ -5,7 +5,7 @@ import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { MonsterStore } from "@/types"; import { ASEvent, MonsterStore } from "@/types";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
@@ -16,7 +16,6 @@ export default function AsBar() {
setAsConfig setAsConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore() const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
@@ -27,20 +26,52 @@ export default function AsBar() {
return mapAS[as_config.event_id.toString()] return mapAS[as_config.event_id.toString()]
}, [as_config, mapAS]) }, [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(() => { const buffList = useMemo(() => {
if (!eventSelected) return []; if (!eventSelected) return [];
if (as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") { if (as_config.floor_side === "firstNode") {
return eventSelected?.BuffList1 ?? []; return eventSelected?.BuffList1 ?? [];
} }
if (as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") { if (as_config.floor_side === "secondNode") {
return eventSelected?.BuffList2 ?? []; return eventSelected?.BuffList2 ?? [];
} }
if (as_config.floor_side === "thirdNode" && eventSelected?.BuffList3) {
return eventSelected?.BuffList3 ?? [];
}
return []; return [];
}, [as_config.floor_side, eventSelected]); }, [as_config.floor_side, eventSelected]);
useEffect(() => { useEffect(() => {
if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return
const newBattleConfig = structuredClone(as_config) const newBattleConfig = structuredClone(as_config)
@@ -65,67 +96,36 @@ export default function AsBar() {
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
if ((as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower")
&& challengeSelected.EventList1.length > 0) { let targetEventList: ASEvent[] = []
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID if (as_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
for (const wave of challengeSelected.EventList1[0].MonsterList) { 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[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value, monster_id: value,
level: challengeSelected.EventList1[0].Level, level: targetEventList[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,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setAsConfig(newBattleConfig) setAsConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
eventSelected,
mapAS, mapAS,
as_config.event_id, as_config.event_id,
as_config.challenge_id, as_config.challenge_id,
@@ -186,10 +186,9 @@ export default function AsBar() {
onChange={(e) => setAsConfig({ ...as_config, floor_side: e.target.value })} onChange={(e) => setAsConfig({ ...as_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option> {floorSideList.map((side) => {
<option value="Lower">{transI18n("lower")}</option> return <option key={side.id} value={side.id}>{side.name}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option> })}
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
</select> </select>
</div> </div>
</div> </div>
@@ -233,182 +232,107 @@ export default function AsBar() {
{/* Enemy Waves */} {/* Enemy Waves */}
{(as_config?.challenge_id ?? 0) !== 0 && ( {(as_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* First Half */} {floorSideList.map((side, i) => {
<div className="rounded-xl p-4 mt-2 border border-warning"> const eventList = side.id === "firstNode"
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2> ? 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) => ( if (!eventList || eventList.length === 0) return null;
<div key={waveIndex} className="mb-6"> const targetEvent = eventList[0];
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()],
challengeSelected?.EventList1?.[0]?.EliteGroup,
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
challengeSelected?.EventList1?.[0]?.Level,
hardLevelConfig,
eliteConfig
);
return (
<div
key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
>
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {challengeSelected?.EventList1[0].Level}
</div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> return (
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && ( <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<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"> <h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
<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"> {targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div className="flex flex-col space-y-1.5"> <div key={waveIndex} className="mb-6">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<span className="text-xs font-semibold text-error">HP</span> <div className="flex flex-wrap gap-2 mt-2">
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> {Object.values(wave).map((waveValue, enemyIndex) => {
</div> const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()],
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> targetEvent?.EliteGroup,
<span className="text-xs font-semibold text-info">Speed</span> targetEvent?.HardLevelGroup,
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> targetEvent?.Level,
</div> hardLevelConfig,
eliteConfig
<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> return (
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <div
</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>
<div className="mt-3 pt-2 border-t border-base-300 flex flex-col items-center"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5"> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
Weakness <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">
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
key={iconIndex}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
width={40} width={150}
height={40} height={150}
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" 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>
</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>
)} )}

View File

@@ -7,7 +7,7 @@ import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types"; import { MoCEvent, MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function MocBar() { export default function MocBar() {
@@ -28,6 +28,33 @@ export default function MocBar() {
return mapMoc[moc_config.event_id.toString()] return mapMoc[moc_config.event_id.toString()]
}, [moc_config, mapMoc]) }, [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(() => { useEffect(() => {
if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
@@ -47,62 +74,36 @@ export default function MocBar() {
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 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 let targetEventList: MoCEvent[] = []
for (const wave of challengeSelected.EventList1[0].MonsterList) { 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[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value, monster_id: value,
level: challengeSelected.EventList1[0].Level, level: targetEventList[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if ((moc_config.floor_side === "Lower" || moc_config.floor_side === "Lower -> Upper") && challengeSelected.EventList2.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if (moc_config.floor_side === "Lower -> Upper" && challengeSelected.EventList1.length > 0) {
for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
} else if (moc_config.floor_side === "Upper -> Lower" && challengeSelected.EventList2.length > 0) {
for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList2[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setMocConfig(newBattleConfig) setMocConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected,
eventSelected,
moc_config.event_id, moc_config.event_id,
moc_config.challenge_id, moc_config.challenge_id,
moc_config.floor_side, moc_config.floor_side,
@@ -168,10 +169,9 @@ export default function MocBar() {
onChange={(e) => setMocConfig({ ...moc_config, floor_side: e.target.value })} onChange={(e) => setMocConfig({ ...moc_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option> {floorSideList.map((side) => (
<option value="Lower">{transI18n("lower")}</option> <option key={side.id} value={side.id}>{side.name}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option> ))}
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
</select> </select>
</div> </div>
@@ -232,182 +232,108 @@ export default function MocBar() {
{/* Enemy Waves */} {/* Enemy Waves */}
{(moc_config?.challenge_id ?? 0) !== 0 && ( {(moc_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* First Half */} {floorSideList.map((side, i) => {
<div className="rounded-xl p-4 mt-2 border border-warning"> const eventList = side.id === "firstNode"
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2> ? 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) => ( if (!eventList || eventList.length === 0) return null;
<div key={waveIndex} className="mb-6"> const targetEvent = eventList[0];
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()],
challengeSelected?.EventList1?.[0]?.EliteGroup,
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
challengeSelected?.EventList1?.[0]?.Level,
hardLevelConfig,
eliteConfig
);
return (
<div
key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
>
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {challengeSelected?.EventList1[0].Level}
</div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> return (
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && ( <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<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"> <h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
<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"> {targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div className="flex flex-col space-y-1.5"> <div key={waveIndex} className="mb-6">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<span className="text-xs font-semibold text-error">HP</span> <div className="flex flex-wrap gap-2 mt-2">
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> {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>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<span className="text-xs font-semibold text-info">Speed</span> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <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>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex flex-col px-1 pb-2 pt-2">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <div className="flex flex-col space-y-1.5">
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <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 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"> </div>
Weakness </div>
</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> })}
{/* 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> </div>
)} )}

View File

@@ -5,7 +5,7 @@ import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { MonsterStore } from "@/types"; import { MonsterStore, PFEvent } from "@/types";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
@@ -26,6 +26,33 @@ export default function PfBar() {
return mapPF[pf_config.event_id.toString()] return mapPF[pf_config.event_id.toString()]
}, [pf_config, mapPF]) }, [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(() => { useEffect(() => {
if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) { if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) {
@@ -50,63 +77,36 @@ export default function PfBar() {
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 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 let targetEventList: PFEvent[] = []
for (const wave of challengeSelected.EventList1[0].MonsterList) { 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[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value, monster_id: value as number,
level: challengeSelected.EventList1[0].Level, level: targetEventList[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,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setPfConfig(newBattleConfig) setPfConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
eventSelected,
pf_config.event_id, pf_config.event_id,
pf_config.challenge_id, pf_config.challenge_id,
pf_config.floor_side, pf_config.floor_side,
@@ -167,10 +167,9 @@ export default function PfBar() {
onChange={(e) => setPfConfig({ ...pf_config, floor_side: e.target.value })} onChange={(e) => setPfConfig({ ...pf_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Upper">{transI18n("upper")}</option> {floorSideList.map((side) => (
<option value="Lower">{transI18n("lower")}</option> <option key={side.id} value={side.id}>{side.name}</option>
<option value="Upper -> Lower">{transI18n("upperToLower")}</option> ))}
<option value="Lower -> Upper">{transI18n("lowerToUpper")}</option>
</select> </select>
</div> </div>
</div> </div>
@@ -232,182 +231,108 @@ export default function PfBar() {
{/* Enemy Waves */} {/* Enemy Waves */}
{(pf_config?.challenge_id ?? 0) !== 0 && ( {(pf_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* First Half */} {floorSideList.map((side, i) => {
<div className="rounded-xl p-4 mt-2 border border-warning"> const eventList = side.id === "firstNode"
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("firstHalfEnemies")}</h2> ? challengeSelected?.EventList1
: side.id === "secondNode"
? challengeSelected?.EventList2
: side.id === "thirdNode"
? eventSelected?.Tierce?.EventList
: [];
{challengeSelected && Object.values(challengeSelected.EventList1?.[0]?.Infinite || []).map((waveValue, waveIndex) => ( if (!eventList || eventList.length === 0) return null;
<div key={waveIndex} className="mb-6"> const targetEvent = eventList[0];
<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="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> return (
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && ( <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<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"> <h2 className="text-2xl font-bold mb-2 text-info">{side.wave}</h2>
<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"> {targetEvent && Object.values(targetEvent.Infinite || []).map((waveValue, waveIndex) => (
<div className="flex flex-col space-y-1.5"> <div key={waveIndex} className="mb-6">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<span className="text-xs font-semibold text-error">HP</span> <div className="flex flex-wrap gap-2 mt-2">
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> {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>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<span className="text-xs font-semibold text-info">Speed</span> {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <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>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex flex-col px-1 pb-2 pt-2">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <div className="flex flex-col space-y-1.5">
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <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 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"> </div>
Weakness </div>
</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> })}
{/* 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> </div>
)} )}

View File

@@ -9,7 +9,9 @@ export interface ASGroupDetail {
EndTime: string; EndTime: string;
BuffList1: ASBuff[]; BuffList1: ASBuff[];
BuffList2: ASBuff[]; BuffList2: ASBuff[];
BuffList3: ASBuff[] | null;
Level: ASLevel[]; Level: ASLevel[];
Tierce: ASTierceLevel | null;
} }
export interface ASBuff { export interface ASBuff {
@@ -21,6 +23,16 @@ export interface ASBuff {
ExtraList?: ExtraEffect[]; ExtraList?: ExtraEffect[];
} }
export interface ASTierceLevel {
ID: number;
PreChallenge: number;
Name: Record<string, string>;
Target: ASTarget[];
DamageType: string[];
TurnLimit: number;
EventList: ASEvent[];
}
export interface ASLevel { export interface ASLevel {
Floor: number; Floor: number;
ID: number; ID: number;
@@ -35,6 +47,7 @@ export interface ASLevel {
EventList2: ASEvent[]; EventList2: ASEvent[];
Monster1: ASMonster; Monster1: ASMonster;
Monster2: ASMonster; Monster2: ASMonster;
Monster3: ASMonster | null;
} }
export interface ASTarget { export interface ASTarget {

View File

@@ -7,6 +7,17 @@ export interface MOCGroupDetail {
BeginTime: string; BeginTime: string;
EndTime: string; EndTime: string;
Level: MoCLevel[]; 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 { export interface MoCLevel {

View File

@@ -6,7 +6,8 @@ export interface PFGroupDetail {
EndTime: string; EndTime: string;
SubOption: MazeBuff[]; SubOption: MazeBuff[];
Option: MazeBuff[]; Option: MazeBuff[];
Level: LevelData[]; Level: PFLevel[];
Tierce: PFTierceLevel | null;
} }
export interface MazeBuff { export interface MazeBuff {
@@ -17,7 +18,17 @@ export interface MazeBuff {
Desc: Record<string, string>; 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; Floor: number;
ID: number; ID: number;
StageNum: number; StageNum: number;
@@ -26,8 +37,8 @@ export interface LevelData {
DamageType1: string[]; DamageType1: string[];
DamageType2: string[]; DamageType2: string[];
MazeBuff: MazeBuff[]; MazeBuff: MazeBuff[];
EventList1: StageConfig[]; EventList1: PFEvent[];
EventList2: StageConfig[]; EventList2: PFEvent[];
TurnLimit: number; TurnLimit: number;
BattleTarget: BattleTarget[]; BattleTarget: BattleTarget[];
ClearScore: number; ClearScore: number;
@@ -45,7 +56,7 @@ export interface BattleTarget {
Name: Record<string, string>; Name: Record<string, string>;
} }
export interface StageConfig { export interface PFEvent {
ID: number; ID: number;
Name: Record<string, string>; Name: Record<string, string>;
HardLevelGroup: number; HardLevelGroup: number;