Refactor code structure for improved readability and maintainability
Gitea Auto Deploy / Deploy-Container (push) Successful in 41s

This commit is contained in:
2026-10-02 23:32:50 +07:00
parent 786ccf7cce
commit e9af27e724
23 changed files with 8040 additions and 142 deletions
Binary file not shown.
+18 -2
View File
@@ -11,18 +11,34 @@ import MultiCharLineChart from "@/components/chart/damageLineForAll";
import DamagePerCycleForAll from "@/components/chart/damagePerCycleForAll";
import DamagePercentChartForAll from "@/components/chart/damagePercentForAll";
import EnemyBar from "@/components/enemybar";
import { useShallow } from "zustand/react/shallow";
import { CharacterBasic, MonsterBasic } from "@/types";
export default function Home() {
const transI18n = useTranslations("DataAnalysisPage");
const { setListAvatar, setMapAvatar, setListEnemy, setMapEnemy } = useAvatarDataStore();
const { setListAvatar, setMapAvatar, setListEnemy, setMapEnemy } = useAvatarDataStore(
useShallow(state => ({
setListAvatar: state.setListAvatar,
setMapAvatar: state.setMapAvatar,
setListEnemy: state.setListEnemy,
setMapEnemy: state.setMapEnemy,
}))
);
const {
totalAV,
totalDamage,
damagePerAV,
turnHistory,
enemyDetail
} = useBattleDataStore();
} = useBattleDataStore(
useShallow(state => ({
totalAV: state.totalAV,
totalDamage: state.totalDamage,
damagePerAV: state.damagePerAV,
turnHistory: state.turnHistory,
enemyDetail: state.enemyDetail,
}))
);
const [expandedCharts, setExpandedCharts] = useState<string[]>([]);
const toggleExpand = (chartId: string) => {
+12 -6
View File
@@ -8,6 +8,7 @@ import { useEffect, useState, useRef } from "react";
import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
import { getNameChar } from "@/helper";
import { useShallow } from "zustand/react/shallow";
import Image from "next/image";
import NameAvatar from "../nameAvatar";
@@ -15,9 +16,17 @@ export default function ActionBar() {
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
const [selectAvatar, setSelectAvatar] = useState<CharacterBasic | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore();
const { listAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore(
useShallow(state => ({
skillHistory: state.skillHistory,
turnHistory: state.turnHistory,
cycleIndex: state.cycleIndex,
waveIndex: state.waveIndex,
maxWave: state.maxWave,
}))
);
const listAvatar = useAvatarDataStore(state => state.listAvatar);
const locale = useLocaleStore(state => state.locale);
const transI18n = useTranslations("DataAnalysisPage");
const turnListRef = useRef<HTMLDivElement>(null);
@@ -39,7 +48,6 @@ export default function ActionBar() {
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsModalOpen(false);
setSelectAvatar(null);
@@ -49,7 +57,6 @@ export default function ActionBar() {
}
};
// Handle ESC key to close modal
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isModalOpen) {
@@ -61,7 +68,6 @@ export default function ActionBar() {
return () => window.removeEventListener('keydown', handleEscKey);
}, [isModalOpen]);
// Scroll to the bottom when new turns are added
useEffect(() => {
if (turnListRef.current && skillHistory.length > 0) {
turnListRef.current.scrollTop = turnListRef.current.scrollHeight;
+16 -8
View File
@@ -13,10 +13,10 @@ interface CharacterCardProps {
}
export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore();
const { avatarDetail } = useBattleDataStore()
const locale = useLocaleStore(state => state.locale);
const avatarDetail = useBattleDataStore(state => state.avatarDetail);
const transI18n = useTranslations("DataAnalysisPage");
const text = getNameChar(locale, transI18n, data)
const text = getNameChar(locale, transI18n, data);
return (
<li className="z-10 flex flex-col w-28 items-center p-1 rounded-md shadow-lg bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300 hover:scale-105 m-1">
@@ -64,16 +64,23 @@ export default function CharacterCard({ data }: CharacterCardProps) {
className="mt-2 text-center text-base font-normal leading-tight"
/>
{avatarDetail && (
(() => {
const stats = avatarDetail?.[Number(data.id)]?.stats;
const hp = stats?.CurrentHP ?? stats?.HP ?? 0;
const maxHp = stats?.MaxHP ?? 100;
const hpPercent = Math.round((hp / maxHp) * 100);
return (
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs text-base-content/70 mx-1">HP:</span>
<span className="text-xs font-medium">
<span className="text-error">
{Number(avatarDetail?.[Number(data.id)]?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
{Number(hp).toLocaleString(undefined, { maximumFractionDigits: 0 })}
</span>
<span className="text-base-content/50">/</span>
<span className="text-base-content/70">
{Number(avatarDetail?.[Number(data.id)]?.stats?.MaxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
{Number(maxHp).toLocaleString(undefined, { maximumFractionDigits: 0 })}
</span>
</span>
</div>
@@ -82,15 +89,16 @@ export default function CharacterCard({ data }: CharacterCardProps) {
<div
className="bg-error h-2.5 rounded-full transition-all duration-300"
style={{
width: `${Math.max(0, Math.min(100, ((avatarDetail?.[Number(data.id)]?.stats?.HP || 0) / (avatarDetail?.[Number(data.id)]?.stats?.MaxHP || 100)) * 100))}%`
width: `${Math.max(0, Math.min(100, (hp / maxHp) * 100))}%`
}}
/>
<span className="absolute inset-0 flex items-center justify-center text-xs text-white font-medium">
{Math.round(((avatarDetail?.[Number(data.id)]?.stats?.HP || 0) / (avatarDetail?.[Number(data.id)]?.stats?.MaxHP || 100)) * 100)}%
{hpPercent}%
</span>
</div>
</div>
);
})()
)}
</li>
+10 -10
View File
@@ -24,19 +24,19 @@ export default function MultiCharLineChart() {
const [mode, setMode] = useState<1 | 2>(2);
const dataByAvatar = useDamageLinesForAll(mode);
const avatarIds = Object.keys(dataByAvatar).map(Number);
const { mapAvatar } = useAvatarDataStore()
const { locale } = useLocaleStore();
const transI18n = useTranslations("DataAnalysisPage")
const mapAvatar = useAvatarDataStore(state => state.mapAvatar);
const locale = useLocaleStore(state => state.locale);
const transI18n = useTranslations("DataAnalysisPage");
const data = {
datasets: avatarIds.map((id, idx) => ({
datasets: avatarIds
.filter(id => id > 0 && mapAvatar?.[id.toString()])
.map((id, idx) => ({
label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]),
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => {
return {
x: x.toFixed(2),
y: y.toFixed(2)
}
}),
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => ({
x: Number(x.toFixed(2)),
y: Number(y.toFixed(2))
})),
borderColor: colors[idx % colors.length],
backgroundColor: colors[idx % colors.length],
fill: false,
+15 -4
View File
@@ -16,6 +16,8 @@ import { Bar } from 'react-chartjs-2';
import { useTranslations } from 'next-intl';
import {attackTypeToString} from "@/types";
import { useShallow } from 'zustand/react/shallow';
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
const colorPalette = [
@@ -47,9 +49,14 @@ const borderPalette = colorPalette.map((c) => c.replace('0.6', '1'));
export default function DamagePerAvatarForAll() {
const transI18n = useTranslations("DataAnalysisPage");
const { lineup, skillHistory } = useBattleDataStore();
const { mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const { lineup, skillHistory } = useBattleDataStore(
useShallow(state => ({
lineup: state.lineup,
skillHistory: state.skillHistory,
}))
);
const mapAvatar = useAvatarDataStore(state => state.mapAvatar);
const locale = useLocaleStore(state => state.locale);
const [mode, setMode] = useState<number>(2);
@@ -93,7 +100,11 @@ export default function DamagePerAvatarForAll() {
const damageTypesSet = new Set<string>();
avatarMap.forEach(({ avatarId }) => {
skillHistory.filter(s => s.avatarId === avatarId).forEach(s =>
s.damageDetail?.forEach(d => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase())))
s.damageDetail?.forEach(d => {
if (d?.damage_type !== undefined) {
damageTypesSet.add(transI18n(attackTypeToString(d.damage_type).toLowerCase()));
}
})
);
});
const damageTypes = Array.from(damageTypesSet);
@@ -2,11 +2,11 @@
import { Bar } from "react-chartjs-2";
import { ChartOptions } from "chart.js";
import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle";
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from "chart.js";
import { useTranslations } from "next-intl";
import { useState } from "react";
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
export default function DamagePerCycleForAll() {
const [mode, setMode] = useState<0 | 1 | 2>(0);
const dataRaw = useDamagePerCycleForAll(mode);
@@ -3,11 +3,11 @@ import { Bar } from "react-chartjs-2";
import { ChartOptions } from "chart.js";
import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle";
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from "chart.js";
import { useTranslations } from "next-intl";
import { useState } from "react";
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
export function DamagePerCycleForOne({
avatarId,
}: {
+7 -5
View File
@@ -27,13 +27,15 @@ export default function DamagePercentChartForAll() {
const [mode, setMode] = useState<1 | 2>(1);
const damageByAvatar = useDamagePercentPerAvatar();
const damageByType = useDamagePercentByType();
const { mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const mapAvatar = useAvatarDataStore(state => state.mapAvatar);
const locale = useLocaleStore(state => state.locale);
const transI18n = useTranslations("DataAnalysisPage");
const filteredDamageByAvatar = damageByAvatar.filter(d => d.avatarId > 0 && mapAvatar?.[d.avatarId.toString()]);
const chartData = {
labels: (mode === 1
? damageByAvatar.map(d =>
? filteredDamageByAvatar.map(d =>
getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()])
)
: damageByType.map(d => transI18n(d.type.toLowerCase()))
@@ -42,8 +44,8 @@ export default function DamagePercentChartForAll() {
{
label: '% ' + transI18n("damage"),
data: (mode === 1
? damageByAvatar.map(d => d.percent.toFixed(2))
: damageByType.map(d => d.percent.toFixed(2))
? filteredDamageByAvatar.map(d => Number(d.percent.toFixed(2)))
: damageByType.map(d => Number(d.percent.toFixed(2)))
),
backgroundColor: colors,
borderWidth: 1,
+18 -10
View File
@@ -9,15 +9,22 @@ import useLocaleStore from "@/stores/localeStore";
import NameAvatar from "../nameAvatar";
export default function EnemyBar() {
const { enemyDetail } = useBattleDataStore()
const { mapEnemy } = useAvatarDataStore()
const { locale } = useLocaleStore()
const enemyDetail = useBattleDataStore(state => state.enemyDetail);
const mapEnemy = useAvatarDataStore(state => state.mapEnemy);
const locale = useLocaleStore(state => state.locale);
return (
<div className="p-3 w-full">
<div className="flex gap-3 overflow-x-auto pb-2">
{enemyDetail && Object.values(enemyDetail).filter((enemy) => (
enemy.stats.HP <= enemy.maxHP)).map((enemy, uid) => (
{enemyDetail && Object.values(enemyDetail).filter((enemy) => {
const hp = enemy.stats?.CurrentHP ?? enemy.stats?.HP ?? 0;
return hp <= enemy.maxHP;
}).map((enemy, uid) => {
const hp = enemy.stats?.CurrentHP ?? enemy.stats?.HP ?? 0;
const maxHp = enemy.maxHP || 100;
const hpPercent = Math.round((hp / maxHp) * 100);
return (
<div key={uid} className="bg-base-200 rounded-lg p-3 border border-gray-700 w-52 shrink-0">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
@@ -49,11 +56,11 @@ export default function EnemyBar() {
<div className="text-xs text-base-content/70">HP:</div>
<div className="flex text-xs font-medium">
<div className="text-error">
{Number(enemy?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
{Number(hp).toLocaleString(undefined, { maximumFractionDigits: 0 })}
</div>
<div className="text-base-content/50 mx-1">/</div>
<div className="text-base-content/70">
{Number(enemy?.maxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
{Number(maxHp).toLocaleString(undefined, { maximumFractionDigits: 0 })}
</div>
</div>
</div>
@@ -62,17 +69,18 @@ export default function EnemyBar() {
<div
className="bg-error h-2.5 rounded-full transition-all duration-300"
style={{
width: `${Math.max(0, Math.min(100, ((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100))}%`
width: `${Math.max(0, Math.min(100, (hp / maxHp) * 100))}%`
}}
/>
<div className="absolute inset-0 flex items-center justify-center text-xs text-white font-medium">
{Math.round(((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100)}%
{hpPercent}%
</div>
</div>
</div>
</div>
))}
);
})}
</div>
</div>
)
+28 -8
View File
@@ -13,6 +13,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import Image from "next/image";
import { useShallow } from "zustand/react/shallow";
const themes = [
{ label: "Winter" },
@@ -22,12 +23,33 @@ const themes = [
];
export default function Header() {
const { changeTheme } = useChangeTheme()
const { locale, setLocale } = useLocaleStore()
const { loadBattleDataFromJSON, version } = useBattleDataStore()
const router = useRouter()
const transI18n = useTranslations("DataAnalysisPage")
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore();
const { changeTheme } = useChangeTheme();
const { locale, setLocale } = useLocaleStore(
useShallow(state => ({
locale: state.locale,
setLocale: state.setLocale,
}))
);
const { loadBattleDataFromJSON, version } = useBattleDataStore(
useShallow(state => ({
loadBattleDataFromJSON: state.loadBattleDataFromJSON,
version: state.version,
}))
);
const router = useRouter();
const transI18n = useTranslations("DataAnalysisPage");
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore(
useShallow(state => ({
host: state.host,
port: state.port,
status: state.status,
connectionType: state.connectionType,
setHost: state.setHost,
setPort: state.setPort,
setStatus: state.setStatus,
setConnectionType: state.setConnectionType,
}))
);
const [message, setMessage] = useState({ text: '', type: '' });
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -131,7 +153,6 @@ export default function Header() {
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsModalOpen(false);
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
@@ -140,7 +161,6 @@ export default function Header() {
}
};
// Handle ESC key to close modal
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isModalOpen) {
+16 -7
View File
@@ -13,20 +13,31 @@ import { motion } from "framer-motion";
import { DamageLineForOne } from "../chart/damageLineForOne";
import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne";
import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData";
import { useShallow } from "zustand/react/shallow";
import Image from "next/image";
import NameAvatar from "../nameAvatar";
// import ShowCaseInfo from "../card/showCaseCard";
export default function LineupBar() {
const [selectedCharacter, setSelectedCharacter] = useState<CharacterBasic | undefined>(undefined);
const [isModalOpen, setIsModalOpen] = useState(false);
const transI18n = useTranslations("DataAnalysisPage");
const { lineup, turnHistory, dataAvatar } = useBattleDataStore();
const { listAvatar, mapAvatar } = useAvatarDataStore();
const { locale } = useLocaleStore();
const { lineup, turnHistory, dataAvatar } = useBattleDataStore(
useShallow(state => ({
lineup: state.lineup,
turnHistory: state.turnHistory,
dataAvatar: state.dataAvatar,
}))
);
const { listAvatar, mapAvatar } = useAvatarDataStore(
useShallow(state => ({
listAvatar: state.listAvatar,
mapAvatar: state.mapAvatar,
}))
);
const locale = useLocaleStore(state => state.locale);
const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0);
const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0)
const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0);
const lineupAvatars = listAvatar?.filter(item =>
@@ -42,7 +53,6 @@ export default function LineupBar() {
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsModalOpen(false);
setSelectedCharacter(undefined);
@@ -52,7 +62,6 @@ export default function LineupBar() {
}
};
// Handle ESC key to close modal
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isModalOpen) {
+3 -10
View File
@@ -2,7 +2,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
import { useMemo } from "react";
export function useCalcTotalDmgAvatar(avatarId: number) {
const { skillHistory } = useBattleDataStore.getState();
const skillHistory = useBattleDataStore(state => state.skillHistory);
return useMemo(() => {
return skillHistory
@@ -13,16 +13,9 @@ export function useCalcTotalDmgAvatar(avatarId: number) {
export function useCalcTotalTurnAvatar(avatarId: number) {
const { turnHistory } = useBattleDataStore.getState();
const turnHistory = useBattleDataStore(state => state.turnHistory);
return useMemo(() => {
const uniqueActionValues = new Set<number>();
turnHistory.forEach(turn => {
if (turn.avatarId === avatarId) {
uniqueActionValues.add(turn.actionValue);
}
});
return uniqueActionValues.size;
return turnHistory.filter(turn => turn.avatarId === avatarId).length;
}, [avatarId, turnHistory]);
}
+10 -2
View File
@@ -1,15 +1,23 @@
import useBattleDataStore from "@/stores/battleDataStore";
import { useShallow } from "zustand/react/shallow";
import { useMemo } from "react";
export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) {
const { skillHistory, turnHistory } = useBattleDataStore.getState();
const { skillHistory, turnHistory } = useBattleDataStore(
useShallow(state => ({
skillHistory: state.skillHistory,
turnHistory: state.turnHistory,
}))
);
return useMemo(() => {
const map = new Map<number, number>();
for (const skill of skillHistory) {
if (skill.avatarId !== avatarId) continue;
const actionValue = turnHistory[skill.turnBattleId].actionValue
const turn = turnHistory[skill.turnBattleId];
if (!turn) continue;
const actionValue = turn.actionValue;
const prev = map.get(actionValue) || 0;
map.set(actionValue, prev + skill.totalDamage);
}
+13 -2
View File
@@ -1,19 +1,30 @@
import useBattleDataStore from "@/stores/battleDataStore";
import { useShallow } from "zustand/react/shallow";
import { useMemo } from "react";
export function useDamageLinesForAll(mode: 1 | 2 = 1) {
const { turnHistory, skillHistory } = useBattleDataStore.getState();
const { turnHistory, skillHistory } = useBattleDataStore(
useShallow(state => ({
turnHistory: state.turnHistory,
skillHistory: state.skillHistory,
}))
);
return useMemo(() => {
const avatarMap = new Map<number, Map<number, number>>();
for (const skill of skillHistory) {
if (!skill.avatarId || skill.avatarId <= 0) continue;
if (!avatarMap.has(skill.avatarId)) {
avatarMap.set(skill.avatarId, new Map());
}
const turn = turnHistory[skill.turnBattleId];
if (!turn) continue;
const charMap = avatarMap.get(skill.avatarId)!;
const actionValue = turnHistory[skill.turnBattleId].actionValue
const actionValue = turn.actionValue;
const prev = charMap.get(actionValue) || 0;
charMap.set(actionValue, prev + skill.totalDamage);
}
+13 -2
View File
@@ -1,4 +1,5 @@
import useBattleDataStore from "@/stores/battleDataStore";
import { useShallow } from "zustand/react/shallow";
import { useTranslations } from "next-intl";
import { useMemo } from "react";
@@ -6,7 +7,12 @@ import { useMemo } from "react";
type Mode = 0 | 1 | 2;
export function useDamagePerCycleForOne(avatarId: number, mode: Mode) {
const { skillHistory, turnHistory } = useBattleDataStore.getState();
const { skillHistory, turnHistory } = useBattleDataStore(
useShallow(state => ({
skillHistory: state.skillHistory,
turnHistory: state.turnHistory,
}))
);
const transI18n = useTranslations("DataAnalysisPage");
return useMemo(() => {
const damageMap = new Map<string, number>();
@@ -39,7 +45,12 @@ export function useDamagePerCycleForOne(avatarId: number, mode: Mode) {
export function useDamagePerCycleForAll(mode: Mode) {
const { skillHistory, turnHistory } = useBattleDataStore.getState();
const { skillHistory, turnHistory } = useBattleDataStore(
useShallow(state => ({
skillHistory: state.skillHistory,
turnHistory: state.turnHistory,
}))
);
const transI18n = useTranslations("DataAnalysisPage");
return useMemo(() => {
const damageMap = new Map<string, number>();
+2 -2
View File
@@ -3,7 +3,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
import {attackTypeToString} from "@/types";
export function useDamagePercentByType() {
const { skillHistory } = useBattleDataStore.getState();
const skillHistory = useBattleDataStore(state => state.skillHistory);
return useMemo(() => {
const dmgByType = new Map<string, number>();
@@ -11,7 +11,7 @@ export function useDamagePercentByType() {
skillHistory.forEach(skill => {
skill.damageDetail.forEach(detail => {
const type = detail.damage_type;
if (!type) return;
if (type === undefined) return;
dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage);
});
});
+2 -1
View File
@@ -2,12 +2,13 @@ import useBattleDataStore from "@/stores/battleDataStore";
import { useMemo } from "react";
export function useDamagePercentPerAvatar() {
const { skillHistory } = useBattleDataStore.getState();
const skillHistory = useBattleDataStore(state => state.skillHistory);
return useMemo(() => {
const dmgByAvatar = new Map<number, number>();
skillHistory.forEach(t => {
if (!t.avatarId || t.avatarId <= 0) return;
dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage);
});
+2 -1
View File
@@ -4,7 +4,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
import {attackTypeToString} from "@/types";
export function useDamageByTypeForAvatar(avatarId: number) {
const { skillHistory } = useBattleDataStore.getState();
const skillHistory = useBattleDataStore(state => state.skillHistory);
return useMemo(() => {
const dmgMap = new Map<string, number>();
@@ -14,6 +14,7 @@ export function useDamageByTypeForAvatar(avatarId: number) {
for (const damage of skill.damageDetail) {
const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || "";
if (!type) continue;
dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
import {attackTypeToString} from "@/types";
export function useSkillDamageForAvatar(avatarId: number) {
const { skillHistory } = useBattleDataStore.getState();
const skillHistory = useBattleDataStore(state => state.skillHistory);
return useMemo(() => {
const dmgMap = new Map<string, number>();
+24 -18
View File
@@ -1,4 +1,4 @@
import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatChangeType, UpdateTeamFormationType, Team, ParseAttackType } from '@/types';
import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatChangeType, UpdateTeamFormationType, Team, ParseAttackType, AttackType } from '@/types';
import { InitializeEnemyType } from '@/types/enemy';
import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
import { create } from 'zustand'
@@ -54,27 +54,33 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
avatarDetail: undefined,
enemyDetail: undefined,
loadBattleDataFromJSON: (data: BattleDataStateJson) => {
const skillHistory = data.skillHistory.map(it => {
it.damageDetail = it.damageDetail.map(it => {
const skillHistory = (data.skillHistory || []).map(it => {
const rawDetails = it.damageDetail || [];
const damageDetail = rawDetails.map(d => {
const rawType = d.damage_type !== undefined ? d.damage_type : (d as { type?: AttackType | string }).type;
return {
...d,
damage_type: ParseAttackType(rawType)
};
});
return {
...it,
damage_type: ParseAttackType(it.damage_type)
}
})
return it
})
skillType: ParseAttackType(it.skillType),
damageDetail
};
});
set({
lineup: data.lineup,
turnHistory: data.turnHistory,
lineup: data.lineup || [],
turnHistory: data.turnHistory || [],
skillHistory: skillHistory,
dataAvatar: data.dataAvatar,
totalAV: data.totalAV,
totalDamage: data.totalDamage,
damagePerAV: data.damagePerAV,
cycleIndex: data.cycleIndex,
waveIndex: data.waveIndex,
maxWave: data.maxWave,
maxCycle: data.maxCycle,
dataAvatar: data.dataAvatar || [],
totalAV: data.totalAV || 0,
totalDamage: data.totalDamage || 0,
damagePerAV: data.damagePerAV || 0,
cycleIndex: data.cycleIndex || 0,
waveIndex: data.waveIndex || 1,
maxWave: data.maxWave ?? Infinity,
maxCycle: data.maxCycle ?? Infinity,
version: data.version,
avatarDetail: data.avatarDetail,
enemyDetail: data.enemyDetail
+23 -11
View File
@@ -12,6 +12,7 @@ export interface DamageDetailType {
damage: number;
overkill_damage?: number;
damage_type?: AttackType;
type?: AttackType | string;
}
@@ -35,23 +36,32 @@ export enum AttackType {
const attackTypeMap: Record<string, AttackType> = {
Talent: AttackType.Unknown,
Basic: AttackType.Normal,
Skill: AttackType.BPSkill,
Ultimate: AttackType.Ultra,
Unknown: AttackType.Unknown,
Normal: AttackType.Normal,
BPSkill: AttackType.BPSkill,
Ultra: AttackType.Ultra,
QTE: AttackType.QTE,
DOT: AttackType.DOT,
DoT: AttackType.DOT,
Pursued: AttackType.Pursued,
Maze: AttackType.Maze,
MazeNormal: AttackType.MazeNormal,
Insert: AttackType.Insert,
ElementDamage: AttackType.ElementDamage,
Level: AttackType.Level,
Servant: AttackType.Servant,
TrueDamage: AttackType.TrueDamage,
ElationDamage: AttackType.ElationDamage,
Talent: AttackType.Unknown,
Basic: AttackType.Normal,
Skill: AttackType.BPSkill,
Ultimate: AttackType.Ultra,
Additional: AttackType.Pursued,
Technique: AttackType.Maze,
MazeNormal: AttackType.MazeNormal,
"Follow-up": AttackType.Insert,
"Follow-Up": AttackType.Insert,
"Elemental Damage": AttackType.ElementDamage,
Break: AttackType.ElementDamage,
Level: AttackType.Level,
Servant: AttackType.Servant,
"True Damage": AttackType.TrueDamage,
True: AttackType.TrueDamage,
"Elation Damage": AttackType.ElationDamage,
@@ -75,12 +85,14 @@ export function ParseAttackType(type: AttackType | string | undefined): AttackTy
return attackTypeMap[type] ?? AttackType.Unknown;
}
export function attackTypeToString(type: AttackType | undefined): string {
if (type === undefined) {
return ""
export function attackTypeToString(type: AttackType | string | undefined): string {
if (type === undefined || type === null) {
return "";
}
switch (type) {
const parsed = typeof type === "string" ? ParseAttackType(type) : type;
switch (parsed) {
case AttackType.Unknown: return "Talent";
case AttackType.Normal: return "Basic";
case AttackType.BPSkill: return "Skill";
+7775
View File
File diff suppressed because it is too large Load Diff