update for 3.3.1

This commit is contained in:
2025-05-24 15:55:48 +07:00
parent 7782691604
commit dafd0af23d
28 changed files with 513 additions and 161 deletions

View File

@@ -59,7 +59,7 @@
"knight": "存护",
"mage": "智识",
"priest": "丰饶",
"rouge": "巡猎",
"rogue": "巡猎",
"shaman": "同协",
"warlock": "虚无",
"memory": "记忆",

View File

@@ -59,7 +59,7 @@
"knight": "Preservation",
"mage": "Erudition",
"priest": "Abundance",
"rouge": "The Hunt",
"rogue": "The Hunt",
"shaman": "Harmony",
"warlock": "Nihility",
"memory": "Remembrance",

View File

@@ -59,7 +59,7 @@
"knight": "存護",
"mage": "知恵",
"priest": "豊穣",
"rouge": "巡狩",
"rogue": "巡狩",
"shaman": "調和",
"warlock": "虚無",
"memory": "記憶",

View File

@@ -59,7 +59,7 @@
"knight": "보존",
"mage": "지혜",
"priest": "풍요",
"rouge": "사냥",
"rogue": "사냥",
"shaman": "조화",
"warlock": "허무",
"memory": "기억",

View File

@@ -13,7 +13,7 @@
"actionValue": "Giá trị hành động",
"character": "Nhân vật",
"id": "ID",
"path": "Đường dẫn",
"path": "Vận mệnh",
"rarity": "Số sao",
"element": "Nguyên tố",
"totalTurn": "Tổng lượt",
@@ -59,7 +59,7 @@
"knight": "Bảo Hộ",
"mage": "Tri Thức",
"priest": "Phong Phú",
"rouge": "Săn Bắn",
"rogue": "Săn Bắn",
"shaman": "Hài Hòa",
"warlock": "Hư Vô",
"memory": "Ký Ức",

View File

@@ -59,7 +59,7 @@
"knight": "存护",
"mage": "智识",
"priest": "丰饶",
"rouge": "巡猎",
"rogue": "巡猎",
"shaman": "同协",
"warlock": "虚无",
"memory": "记忆",

View File

@@ -10,6 +10,7 @@ import DamagePerAvatarForAll from "@/components/chart/damagePerAvatarForAll";
import MultiCharLineChart from "@/components/chart/damageLineForAll";
import DamagePerCycleForAll from "@/components/chart/damagePerCycleForAll";
import DamagePercentChartForAll from "@/components/chart/damagePercentForAll";
import EnemyBar from "@/components/enemybar";
export default function Home() {
const transI18n = useTranslations("DataAnalysisPage");
@@ -18,7 +19,8 @@ export default function Home() {
totalAV,
totalDamage,
damagePerAV,
turnHistory
turnHistory,
enemyDetail
} = useBattleDataStore();
const [expandedCharts, setExpandedCharts] = useState<string[]>([]);
@@ -55,22 +57,22 @@ export default function Home() {
<div className="grid grid-cols-2 gap-2 mb-3">
<div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-primary text-primary-content text-center shadow-md">
{transI18n("totalDamage")}
<div>{Number(totalDamage).toFixed(2)}</div>
<div>{Number(totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</div>
</div>
<div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-secondary text-secondary-content text-center shadow-md">
{transI18n("totalAV")}
<div>{Number(totalAV).toFixed(2)}</div>
<div>{Number(totalAV).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</div>
</div>
<div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-accent text-accent-content text-center shadow-md">
{transI18n("damagePerAV")}
<div>{Number(damagePerAV).toFixed(2)}</div>
<div>{Number(damagePerAV).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</div>
</div>
<div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-warning text-warning-content text-center shadow-md">
{transI18n("totalTurn")}
<div>{turnHistory.filter(it => it.avatarId && it.avatarId != -1).length}</div>
</div>
</div>
{enemyDetail && <EnemyBar />}
<div className="rounded-lg p-2 shadow-md flex-grow">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">

View File

@@ -140,7 +140,7 @@ export default function ActionBar() {
{`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`}
</div>
<div className="text-primary text-xs max-w-full">
{`${transI18n("totalDamage")}: ${turn.totalDamage.toFixed(2)}`}
{`${transI18n("totalDamage")}: ${Number(turn.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`}
</div>
</div>
</div>
@@ -234,11 +234,11 @@ export default function ActionBar() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div className="bg-base-200 rounded-lg p-4 shadow-md">
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("actionValue")}</h4>
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].actionValue.toFixed(2)}</p>
<p className="mt-2">{Number(turnHistory[selectTurn?.turnBattleId].actionValue).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
</div>
<div className="bg-base-200 rounded-lg p-4 shadow-md">
<h4 className="text-lg font-semibold mb-2 text-purple-500 border-b border-purple-300/30 pb-1">{transI18n("totalDamage")}</h4>
<p className="mt-2 font-bold text-lg">{selectTurn?.totalDamage.toFixed(2)}</p>
<p className="mt-2 font-bold text-lg">{Number(selectTurn?.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
</div>
</div>
@@ -252,7 +252,7 @@ export default function ActionBar() {
className="flex flex-col items-start gap-1 p-3 rounded-lg shadow bg-base-200"
>
<span className="text-lg font-semibold text-primary">
{detail.damage.toFixed(2)}
{Number(detail.damage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}
</span>
<span className="text-xs uppercase text-gray-500 tracking-wide">
{transI18n(attackTypeToString(detail?.damage_type).toLowerCase())}

View File

@@ -4,6 +4,9 @@ import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { AvatarHakushiType } from '@/types';
import NameAvatar from '../nameAvatar';
import useBattleDataStore from '@/stores/battleDataStore';
import { useEffect, useMemo, useState } from 'react';
import { AvatarInfo } from '@/types/mics';
interface CharacterCardProps {
data: AvatarHakushiType
@@ -14,12 +17,14 @@ interface CharacterCardProps {
export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore();
const text = getNameChar(locale, data)
const { avatarDetail } = useBattleDataStore()
return (
<li className="z-10 flex flex-col w-28 items-center p-1 rounded-md shadow-lg bg-gradient-to-b from-customStart to-customEnd transform transition-transform duration-300 hover:scale-105 m-1">
<div
className={`w-[80px] rounded-md p-[2px] bg-gradient-to-br ${data.rank === "CombatPowerAvatarRarityType5"
? "from-yellow-400 via-yellow-300 to-yellow-500"
: "from-purple-300 via-purple-200 to-purple-400"
? "from-yellow-400 via-yellow-300 to-yellow-500"
: "from-purple-300 via-purple-200 to-purple-400"
}`}
>
@@ -50,6 +55,36 @@ export default function CharacterCard({ data }: CharacterCardProps) {
text={text}
className="mt-2 text-center text-base font-normal leading-tight"
/>
{avatarDetail && (
<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 })}
</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 })}
</span>
</span>
</div>
<div className="relative w-full bg-base-300 rounded-full h-2.5">
<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))}%`
}}
/>
<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)}%
</span>
</div>
</div>
)}
</li>
);

View File

@@ -0,0 +1,71 @@
"use client"
import useBattleDataStore from "@/stores/battleDataStore";
import Image from "next/image";
function formatEnemyIdForURL(id?: number): string {
const n = id ?? 0;
const adjusted = n.toString().length === 9 ? n / 100 : n;
return adjusted.toFixed(0);
}
export default function EnemyBar() {
const { enemyDetail } = useBattleDataStore()
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?.AV > 0
&& enemy.stats.HP <= enemy.maxHP)).map((enemy, uid) => (
<div key={uid} className="bg-base-200 rounded-lg p-3 border border-gray-700 w-52 flex-shrink-0">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/Monster_${formatEnemyIdForURL(enemy.id)}.webp`}
alt={enemy.name}
width={40}
height={40}
className="object-cover w-10 h-10 rounded-lg"
/>
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold leading-tight truncate overflow-hidden" title={enemy.name}>
{enemy.name}
</h3>
<p className="text-base-content/70 text-xs">Level {enemy.level || 1}</p>
</div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<div className="text-xs text-base-content/70">HP:</div>
<div className="text-xs font-medium">
<div className="text-error">
{Number(enemy?.stats?.HP ?? 0).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 })}
</div>
</div>
</div>
<div className="relative w-full bg-base-300 rounded-full h-2.5">
<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))}%`
}}
/>
<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)}%
</div>
</div>
</div>
</div>
))}
</div>
</div>
)
}

View File

@@ -23,7 +23,7 @@ const themes = [
export default function Header() {
const { changeTheme } = useChangeTheme()
const { locale, setLocale } = useLocaleStore()
const { loadBattleDataFromJSON } = useBattleDataStore()
const { loadBattleDataFromJSON, version } = useBattleDataStore()
const router = useRouter()
const transI18n = useTranslations("DataAnalysisPage")
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore();
@@ -219,6 +219,16 @@ export default function Header() {
</h1>
<p className="text-sm text-gray-500">For Veritas</p>
</a>
{version && (
<div className="px-2">
<div className="inline-flex items-center space-x-2 px-3 py-1.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full shadow-md hover:shadow-lg transition-all duration-200">
<div className="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
<div className="text-xs font-semibold text-white">
{version}
</div>
</div>
</div>
)}
</div>

View File

@@ -209,7 +209,7 @@ export default function LineupBar() {
<p>
{transI18n("eidolons")}: <span className="font-bold">{avatar?.data?.rank}</span>
</p>
<p className="flex items-center space-x-2">
<div className="flex items-center space-x-2">
<span>{transI18n("lightcones")}:</span>
<Image
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${avatar?.Lightcone?.item_id}.webp`}
@@ -218,8 +218,8 @@ export default function LineupBar() {
width={200}
height={200}
/>
</p>
<p className="flex items-center space-x-2 w-full">
</div>
<div className="flex items-center space-x-2 w-full">
<span>{transI18n("relics")}:</span>
<div className="grid grid-cols-3 md:flex md:flex-row w-full">
{relicIds.map(it => (
@@ -233,7 +233,7 @@ export default function LineupBar() {
/>
))}
</div>
</p>
</div>
</>
);
})()}
@@ -256,10 +256,10 @@ export default function LineupBar() {
<ShowCaseInfo></ShowCaseInfo>
</div> */}
<div className="bg-base-200 rounded-lg p-4 shadow-md">
<p className="mt-2 font-bold text-lg text-cyan-500">{transI18n("totalTurn")}: <span className="text-base-content">{totalTurn.toFixed(2)}</span></p>
<p className="mt-2 font-bold text-lg text-cyan-500">{transI18n("totalTurn")}: <span className="text-base-content">{Number(totalTurn).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</span></p>
</div>
<div className="bg-base-200 rounded-lg p-4 shadow-md">
<h4 className="text-lg font-semibold mb-2 text-purple-500">{transI18n("totalDamage")}: <span className="text-base-content">{totalDamage.toFixed(2)}</span></h4>
<h4 className="text-lg font-semibold mb-2 text-purple-500">{transI18n("totalDamage")}: <span className="text-base-content">{Number(totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</span></h4>
</div>
<div className="bg-base-200 rounded-lg p-4 shadow-md">

View File

@@ -14,7 +14,10 @@ export const exportBattleData = (
waveIndex,
dataAvatar,
maxWave,
maxCycle
maxCycle,
version,
avatarDetail,
enemyDetail
} = useBattleDataStore.getState();
const data: BattleDataStateJson = {
@@ -28,7 +31,10 @@ export const exportBattleData = (
cycleIndex,
waveIndex,
maxWave,
maxCycle
maxCycle,
version,
avatarDetail,
enemyDetail
}
const dataStr = JSON.stringify(data, null, 2);

View File

@@ -5,7 +5,7 @@ import axios from 'axios';
export async function checkConnectTcpApi(): Promise<boolean> {
const { host, port, connectionType } = useSocketStore.getState()
let url = `${host}:${port}/check-tcp`
if (connectionType === "FireflyPSLocal") {
if (connectionType === "PS") {
url = "http://localhost:21000/check-tcp"
}
const response = await fetch(url, {

View File

@@ -25,18 +25,22 @@ function safeParse(json: unknown | string) {
export const connectSocket = (): Socket => {
const { host, port, connectionType, setStatus } = useSocketStore.getState();
const {
onSetBattleLineupService,
onTurnEndService,
onUseSkillService,
onKillService,
onDamageService,
onBattleEndService,
onTurnBeginService,
const {
onConnectedService,
onBattleBeginService,
onCreateBattleService,
onSetBattleLineupService,
onDamageService,
onTurnBeginService,
onTurnEndService,
onEntityDefeatedService,
onUseSkillService,
onUpdateWaveService,
onUpdateCycleService,
OnUpdateWaveService
onStatChange,
onUpdateTeamFormation,
onInitializeEnemyService,
onBattleEndService,
onCreateBattleService,
} = useBattleDataStore.getState();
let url = `${host}:${port}`;
@@ -86,68 +90,73 @@ export const connectSocket = (): Socket => {
setStatus(true);
notify(`Kết nối thành công với Socket ID: ${socket?.id}`, 'success');
};
const onBattleBegin = (data: BattleBeginType) => {
notify("Battle Started!", "info")
onBattleBeginService(data)
}
if (isSocketConnected()) onConnect();
socket.on("OnSetBattleLineup", (json) => {
socket.on("Connected", (json) => {
const data = safeParse(json);
if (data) onSetBattleLineupService(data);
if (data) onConnectedService(data);
});
socket.on("OnTurnEnd", (json) => {
const data = safeParse(json);
if (data) onTurnEndService(data);
});
socket.on("OnUseSkill", (json) => {
const data = safeParse(json);
if (data) onUseSkillService(data);
});
socket.on("OnKill", (json) => {
const data = safeParse(json);
if (data) onKillService(data);
});
socket.on("OnDamage", (json) => {
const data = safeParse(json);
if (data) onDamageService(data);
});
socket.on("OnBattleBegin", (json) => {
const data = safeParse(json);
if (data) onBattleBegin(data);
});
socket.on("OnSetBattleLineup", (json) => {
const data = safeParse(json);
if (data) onSetBattleLineupService(data);
});
socket.on("OnDamage", (json) => {
const data = safeParse(json);
if (data) onDamageService(data);
});
socket.on("OnTurnBegin", (json) => {
const data = safeParse(json);
if (data) onTurnBeginService(data);
});
socket.on("OnBattleEnd", (json) => {
socket.on("OnTurnEnd", (json) => {
const data = safeParse(json);
if (data) onBattleEndService(data);
if (data) onTurnEndService(data);
});
socket.on("OnEntityDefeated", (json) => {
const data = safeParse(json);
if (data) onEntityDefeatedService(data);
});
socket.on("OnUseSkill", (json) => {
const data = safeParse(json);
if (data) onUseSkillService(data);
});
socket.on("OnUpdateWave", (json) => {
const data = safeParse(json);
if (data) onUpdateWaveService(data);
});
socket.on("OnUpdateCycle", (json) => {
const data = safeParse(json);
if (data) onUpdateCycleService(data);
});
socket.on("OnUpdateWave", (json) => {
socket.on("OnStatChange", (json) => {
const data = safeParse(json);
if (data) OnUpdateWaveService(data);
if (data) onStatChange(data);
});
socket.on("OnUpdateTeamFormation", (json) => {
const data = safeParse(json);
if (data) onUpdateTeamFormation(data);
});
socket.on("OnInitializeEnemy", (json) => {
const data = safeParse(json);
if (data) onInitializeEnemyService(data);
});
socket.on("OnBattleEnd", (json) => {
const data = safeParse(json);
if (data) onBattleEndService(data);
});
socket.on("OnCreateBattle", (json) => {
const data = safeParse(json);
if (data) onCreateBattleService(data);
});
socket.on("Error", (msg: string) => {
console.error("Server Error:", msg);
});
@@ -157,34 +166,42 @@ export const connectSocket = (): Socket => {
export const disconnectSocket = (): void => {
const {
onSetBattleLineupService,
onTurnEndService,
onUseSkillService,
onKillService,
onDamageService,
onBattleEndService,
onTurnBeginService,
onConnectedService,
onBattleBeginService,
onCreateBattleService,
onSetBattleLineupService,
onDamageService,
onTurnBeginService,
onTurnEndService,
onEntityDefeatedService,
onUseSkillService,
onUpdateWaveService,
onUpdateCycleService,
OnUpdateWaveService
onStatChange,
onUpdateTeamFormation,
onInitializeEnemyService,
onBattleEndService,
onCreateBattleService,
} = useBattleDataStore.getState();
const onBattleBegin = (data: BattleBeginType) => {
notify("Battle Started!", "info")
onBattleBeginService(data)
}
if (socket) {
socket.off("Connected", (json) => onConnectedService(JSON.parse(json)));
socket.off("OnBattleBegin", (json) => onBattleBegin(JSON.parse(json)));
socket.off("OnSetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json)));
socket.off("OnTurnEnd", (json) => onTurnEndService(JSON.parse(json)));
socket.off("OnUseSkill", (json) => onUseSkillService(JSON.parse(json)));
socket.off("OnKill", (json) => onKillService(JSON.parse(json)));
socket.off("OnEntityDefeated", (json) => onEntityDefeatedService(JSON.parse(json)));
socket.off("OnDamage", (json) => onDamageService(JSON.parse(json)));
socket.off('OnBattleBegin', (json) => onBattleBegin(JSON.parse(json)));
socket.off('OnTurnBegin', (json) => onTurnBeginService(JSON.parse(json)));
socket.off('OnBattleEnd', (json) => onBattleEndService(JSON.parse(json)));
socket.off('OnUpdateCycle', (json) => onUpdateCycleService(JSON.parse(json)));
socket.off('OnUpdateWave', (json) => OnUpdateWaveService(JSON.parse(json)));
socket.off('OnUpdateWave', (json) => onUpdateWaveService(JSON.parse(json)));
socket.off('OnCreateBattle', (json) => onCreateBattleService(JSON.parse(json)));
socket.off('OnStatChange', (json) => onStatChange(JSON.parse(json)));
socket.off('OnUpdateTeamFormation', (json) => onUpdateTeamFormation(JSON.parse(json)));
socket.off('OnInitializeEnemy', (json) => onInitializeEnemyService(JSON.parse(json)));
socket.offAny();
socket.disconnect();
useSocketStore.getState().setStatus(false);

View File

@@ -1,5 +1,6 @@
import { AttackResultType, AvatarAnalysisJson, AvatarSkillType, BattleBeginType, BattleEndType, DamageDetailType, KillType, LineUpType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType } from '@/types';
import { AvatarBattleInfo, BattleDataStateJson, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatType, StatChangeType, UpdateTeamFormationType } from '@/types';
import { InitializeEnemyType } from '@/types/enemy';
import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
import { create } from 'zustand'
@@ -14,19 +15,26 @@ interface BattleDataState {
cycleIndex: number;
waveIndex: number;
maxWave: number;
maxCycle: number
maxCycle: number;
version?: string;
avatarDetail?: Record<number, AvatarInfo>;
enemyDetail?: Record<number, EnemyInfo>;
onSetBattleLineupService: (data: LineUpType) => void;
onTurnEndService: (data: TurnEndType) => void;
onBattleEndService: (data: BattleEndType) => void;
onUseSkillService: (data: AvatarSkillType) => void;
onKillService: (data: KillType) => void
onDamageService: (data: AttackResultType) => void;
onConnectedService: (data: VersionType) => void
onBattleBeginService: (data: BattleBeginType) => void;
onSetBattleLineupService: (data: SetBattleLineupType) => void;
onDamageService: (data: DamageType) => void;
onTurnBeginService: (data: TurnBeginType) => void;
onCreateBattleService: (data: AvatarAnalysisJson[]) => void;
OnUpdateWaveService: (data: UpdateWaveType) => void;
onTurnEndService: (data: TurnEndType) => void;
onEntityDefeatedService: (data: EntityDefeatedType) => void;
onUseSkillService: (data: UseSkillType) => void;
onUpdateWaveService: (data: UpdateWaveType) => void;
onUpdateCycleService: (data: UpdateCycleType) => void;
onStatChange: (data: StatChangeType) => void;
onUpdateTeamFormation: (data: UpdateTeamFormationType) => void;
onInitializeEnemyService: (data: InitializeEnemyType) => void;
onBattleEndService: (data: BattleEndType) => void;
onCreateBattleService: (data: AvatarAnalysisJson[]) => void;
loadBattleDataFromJSON: (data: BattleDataStateJson) => void;
}
@@ -42,7 +50,9 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
waveIndex: 1,
maxWave: Infinity,
maxCycle: Infinity,
version: undefined,
avatarDetail: undefined,
enemyDetail: undefined,
loadBattleDataFromJSON: (data: BattleDataStateJson) => {
set({
lineup: data.lineup,
@@ -55,7 +65,15 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
cycleIndex: data.cycleIndex,
waveIndex: data.waveIndex,
maxWave: data.maxWave,
maxCycle: data.maxCycle
maxCycle: data.maxCycle,
version: data.version,
avatarDetail: data.avatarDetail,
enemyDetail: data.enemyDetail
})
},
onConnectedService: (data: VersionType) => {
set({
version: data.version
})
},
onCreateBattleService: (data: AvatarAnalysisJson[]) => {
@@ -75,36 +93,7 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
turnHistory: updatedHistory
})
},
onDamageService: (data: AttackResultType) => {
const skillHistory = get().skillHistory
const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.id)
if (skillIdx === -1) {
return
}
const newTh = [...skillHistory]
newTh[skillIdx].damageDetail.push({damage: data.damage, damage_type: data?.damage_type} as DamageDetailType)
newTh[skillIdx].totalDamage += data.damage
set({
skillHistory: newTh,
totalDamage: get().totalDamage + data.damage,
damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV)
})
},
onKillService: (data: KillType) => {
const lineups = get().lineup
const avatarIdx = lineups.findIndex(it => it.avatarId === data.attacker.id)
if (avatarIdx === -1) {
return
}
const newLn = [...lineups]
newLn[avatarIdx].isDie = true
set({
lineup: newLn
})
},
onSetBattleLineupService: (data: LineUpType) => {
onSetBattleLineupService: (data: SetBattleLineupType) => {
const lineups: AvatarBattleInfo[] = []
for (const avatar of data.avatars) {
lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo)
@@ -125,13 +114,28 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
waveIndex: 1,
}));
},
onDamageService: (data: DamageType) => {
const skillHistory = get().skillHistory
const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.uid)
if (skillIdx === -1) {
return
}
const newTh = [...skillHistory]
newTh[skillIdx].damageDetail.push({damage: data.damage, damage_type: data?.damage_type} as DamageDetailType)
newTh[skillIdx].totalDamage += data.damage
set({
skillHistory: newTh,
totalDamage: get().totalDamage + data.damage,
damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV)
})
},
onTurnBeginService: (data: TurnBeginType) => {
set((state) => ({
totalAV: data.action_value,
damagePerAV: state.totalDamage / (data.action_value === 0 ? 1 : data.action_value),
turnHistory: [...state.turnHistory, {
avatarId: data?.turn_owner?.id,
avatarId: data?.turn_owner?.uid,
actionValue: data.action_value,
waveIndex: state.waveIndex,
cycleIndex: state.cycleIndex
@@ -146,19 +150,157 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
/ (data.turn_info.action_value === 0 ? 1 : data.turn_info.action_value)
}));
},
onUseSkillService: (data: AvatarSkillType) => {
onEntityDefeatedService: (data: EntityDefeatedType) => {
let avatarDetail = get().avatarDetail
let enemyDetail = get().enemyDetail
if (!enemyDetail) {
enemyDetail = {} as Record<number, EnemyInfo>
}
if (!avatarDetail) {
avatarDetail = {} as Record<number, AvatarInfo>
}
if (data.killer.team === "Player" && enemyDetail[data.entity_defeated.uid]) {
enemyDetail[data.entity_defeated.uid].isDie = true
enemyDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
} else if (data.killer.team === "Enemy" && avatarDetail[data.entity_defeated.uid]) {
avatarDetail[data.entity_defeated.uid].isDie = true
avatarDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
} else {
console.error("onEntityDefeatedService", data)
console.error("onEntityDefeatedService", enemyDetail)
console.error("onEntityDefeatedService", avatarDetail)
}
set({
avatarDetail: avatarDetail,
enemyDetail: enemyDetail
})
},
onUseSkillService: (data: UseSkillType) => {
set((state) => ({
skillHistory: [...state.skillHistory, {
avatarId: data.avatar.id,
avatarId: data.avatar.uid,
damageDetail: [],
totalDamage: 0,
skillType: data.skill.type,
skillName: data.skill.name,
turnBattleId: state.turnHistory.length-1
turnBattleId: state.turnHistory.length-1
} as SkillBattleInfo]
}))
},
onUpdateWaveService: (data: UpdateWaveType) => {
set({
waveIndex: data.wave
})
},
onUpdateCycleService: (data: UpdateCycleType) => {
set({
cycleIndex: data.cycle
})
},
onStatChange: (data: StatChangeType) => {
let avatarDetail = get().avatarDetail
let enemyDetail = get().enemyDetail
if (!enemyDetail) {
enemyDetail = {} as Record<number, EnemyInfo>
}
if (!avatarDetail) {
avatarDetail = {} as Record<number, AvatarInfo>
}
if (data.entity.team === "Player") {
const [key, value] = Object.entries(data.stat)[0]
const uid = data.entity.uid;
if (!avatarDetail[uid]) {
avatarDetail[uid] = {
id: uid,
isDie: false,
killer_uid: -1,
stats: {},
statsHistory: []
};
}
avatarDetail[uid].stats[key] = value
avatarDetail[uid].statsHistory.push({
stats: data.stat,
turnBattleId: get().turnHistory.length-1
})
} else {
const [key, value] = Object.entries(data.stat)[0]
const uid = data.entity.uid;
if (!enemyDetail[uid]) {
enemyDetail[uid] = {
id: uid,
isDie: false,
killer_uid: -1,
name: "",
positionIndex: Object.keys(get().enemyDetail || {}).length,
maxHP: 0,
waveIndex: 0,
level: 0,
stats: {},
statsHistory: []
};
}
enemyDetail[uid].stats[key] = value
enemyDetail[uid].statsHistory.push({
stats: data.stat,
turnBattleId: get().turnHistory.length-1
})
}
set({
avatarDetail: avatarDetail,
enemyDetail: enemyDetail
})
},
onUpdateTeamFormation: (data: UpdateTeamFormationType) => {
let avatarDetail = get().avatarDetail
let enemyDetail = get().enemyDetail
if (!avatarDetail) {
avatarDetail = {} as Record<number, AvatarInfo>
for (const entity of data.entities) {
if (entity.team === "Player" && avatarDetail[entity.uid]) {
}
}
}
if (!enemyDetail) {
enemyDetail = {} as Record<number, EnemyInfo>
for (let i = 0; i < data.entities.length; i++) {
const entity = data.entities[i];
if (entity.team === "Enemy" && enemyDetail[entity.uid]) {
enemyDetail[entity.uid].positionIndex = i
}
}
}
set({
avatarDetail: avatarDetail,
enemyDetail: enemyDetail
})
},
onInitializeEnemyService: (data: InitializeEnemyType) => {
const enemyDetail = get().enemyDetail
if (!enemyDetail) {
return
}
enemyDetail[data.enemy.uid] = {
id: data.enemy.id,
isDie: false,
killer_uid: -1,
positionIndex: enemyDetail[data.enemy.uid].positionIndex,
waveIndex: get().waveIndex,
name: data.enemy.name,
maxHP: data.enemy.base_stats.hp,
level: data.enemy.base_stats.level,
stats: {},
statsHistory: []
}
set({
enemyDetail: enemyDetail
})
},
onBattleEndService: (data: BattleEndType) => {
const lineups: AvatarBattleInfo[] = []
for (const avatar of data.avatars) {
@@ -171,16 +313,6 @@ const useBattleDataStore = create<BattleDataState>((set, get) => ({
damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value)
})
},
OnUpdateWaveService: (data: UpdateWaveType) => {
set({
waveIndex: data.wave
})
},
onUpdateCycleService: (data: UpdateCycleType) => {
set({
cycleIndex: data.cycle
})
}
}));
export default useBattleDataStore;

View File

@@ -1,7 +1,7 @@
import { AvatarType } from "./lineup";
import { EntityType } from "./entity";
export interface AttackResultType {
attacker: AvatarType;
export interface DamageType {
attacker: EntityType;
damage: number;
damage_type?: AttackType
}

View File

@@ -10,9 +10,6 @@ export interface BattleEndType {
action_value: number;
stage_id: number;
}
export interface KillType {
attacker: AvatarType;
}
export interface BattleBeginType {
max_waves: number

12
src/types/enemy.ts Normal file
View File

@@ -0,0 +1,12 @@
import { StatsType } from "./stat";
export interface EnemyType {
id: number;
uid: number;
name: string;
base_stats: StatsType
}
export interface InitializeEnemyType {
enemy: EnemyType
}

9
src/types/entity.ts Normal file
View File

@@ -0,0 +1,9 @@
export interface EntityType {
uid: number;
team: "Player" | "Enemy";
}
export interface EntityDefeatedType {
killer: EntityType,
entity_defeated: EntityType
}

3
src/types/error.ts Normal file
View File

@@ -0,0 +1,3 @@
export interface ErrorType {
msg: string
}

View File

@@ -5,4 +5,7 @@ export * from "./lineup"
export * from "./skill"
export * from "./turn"
export * from "./waveAndCycle"
export * from "./srtools"
export * from "./srtools"
export * from "./version"
export * from "./entity"
export * from "./stat"

View File

@@ -1,8 +1,16 @@
import { EntityType } from "./entity";
export interface AvatarType{
id: number;
name: string;
}
export interface LineUpType {
export interface SetBattleLineupType {
avatars: AvatarType[];
}
export interface UpdateTeamFormationType {
entities: EntityType[],
team: "Player" | "Enemy"
}

View File

@@ -1,9 +1,11 @@
import {AttackType, DamageDetailType} from "./attack";
import { AvatarAnalysisJson } from "./srtools";
import { StatType } from "./stat";
export interface AvatarBattleInfo {
avatarId: number;
isDie: boolean;
isDie?: boolean;
}
export interface SkillBattleInfo {
@@ -33,5 +35,34 @@ export interface BattleDataStateJson {
maxWave: number;
cycleIndex: number,
waveIndex: number,
maxCycle: number
}
maxCycle: number,
version?: string,
avatarDetail?: Record<number, AvatarInfo>;
enemyDetail?: Record<number, EnemyInfo>;
}
export interface StatsHistoryType {
stats: StatType
turnBattleId: number;
}
export interface EnemyInfo {
id: number;
name: string;
maxHP: number;
level: number;
isDie: boolean;
positionIndex: number;
waveIndex: number;
killer_uid: number;
stats: Record<string, number>;
statsHistory: StatsHistoryType[];
}
export interface AvatarInfo {
id: number;
isDie: boolean;
killer_uid: number;
stats: Record<string, number>;
statsHistory: StatsHistoryType[];
}

View File

@@ -1,12 +1,14 @@
import { AvatarType } from "./lineup";
import { AttackType } from "@/types/attack";
import { EntityType } from "./entity";
export interface SkillInfo {
name: string;
type: AttackType;
skill_config_id: number;
}
export interface AvatarSkillType {
avatar: AvatarType;
export interface UseSkillType {
avatar: EntityType;
skill: SkillInfo;
}

13
src/types/stat.ts Normal file
View File

@@ -0,0 +1,13 @@
import { EntityType } from "./entity";
export type StatType = Record<string, number>
export interface StatsType {
level: number;
hp: number;
}
export interface StatChangeType {
entity: EntityType,
stat: StatType,
}

View File

@@ -1,5 +1,4 @@
import { AvatarType } from "./lineup";
import { EntityType } from "./entity";
export interface TurnInfoType {
avatars_turn_damage: number[];
@@ -11,10 +10,9 @@ export interface TurnInfoType {
export interface TurnBeginType {
action_value: number;
turn_owner?: AvatarType
turn_owner?: EntityType | null
}
export interface TurnEndType {
avatars: AvatarType[];
turn_info: TurnInfoType
}

3
src/types/version.ts Normal file
View File

@@ -0,0 +1,3 @@
export interface VersionType {
version: string
}