Refactor TypeScript files for improved readability and consistency
Gitea Auto Deploy / Deploy-Container (push) Successful in 3m21s
Gitea Auto Deploy / Deploy-Container (push) Successful in 3m21s
- Cleaned up imports and formatting in localeStore.ts and socketSettingStore.ts. - Standardized interface definitions and formatting in attack.ts, avatar.ts, battle.ts, enemy.ts, entity.ts, error.ts, index.ts, lineup.ts, mics.ts, skill.ts, srtools.ts, team.ts, turn.ts, version.ts, and waveAndCycle.ts. - Ensured consistent use of semicolons and spacing across all modified files. - Updated tsconfig.json for consistency in formatting.
This commit is contained in:
+14
-14
@@ -1,15 +1,15 @@
|
|||||||
name: Gitea Auto Deploy
|
name: Gitea Auto Deploy
|
||||||
run-name: ${{ gitea.actor }} pushed code 🚀
|
run-name: ${{ gitea.actor }} pushed code 🚀
|
||||||
|
|
||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Deploy-Container:
|
Deploy-Container:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Deploy to Container
|
- name: Deploy to Container
|
||||||
run: |
|
run: |
|
||||||
docker compose up -d --build --remove-orphans
|
docker compose up -d --build --remove-orphans
|
||||||
+56
-56
@@ -1,56 +1,56 @@
|
|||||||
FROM oven/bun:canary-alpine AS base
|
FROM oven/bun:canary-alpine AS base
|
||||||
|
|
||||||
# Install dependencies only when needed
|
# Install dependencies only when needed
|
||||||
FROM base AS deps
|
FROM base AS deps
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
COPY package.json bun.lock ./
|
COPY package.json bun.lock ./
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
# Rebuild the source code only when needed
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Next.js collects completely anonymous telemetry data about general usage.
|
# Next.js collects completely anonymous telemetry data about general usage.
|
||||||
# Learn more here: https://nextjs.org/telemetry
|
# Learn more here: https://nextjs.org/telemetry
|
||||||
# Disable telemetry during the build
|
# Disable telemetry during the build
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
# Production image, copy all the files and run next
|
# Production image, copy all the files and run next
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
# Disable telemetry
|
# Disable telemetry
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder /app/messages ./messages
|
COPY --from=builder /app/messages ./messages
|
||||||
# Set the correct permission for prerender cache
|
# Set the correct permission for prerender cache
|
||||||
RUN mkdir .next
|
RUN mkdir .next
|
||||||
RUN chown nextjs:bun .next
|
RUN chown nextjs:bun .next
|
||||||
|
|
||||||
# Automatically leverage output traces to reduce image size
|
# Automatically leverage output traces to reduce image size
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
|
||||||
# Set hostname to localhost
|
# Set hostname to localhost
|
||||||
ENV HOSTNAME=0.0.0.0
|
ENV HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
CMD ["bun", "server.js"]
|
CMD ["bun", "server.js"]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+15
-15
@@ -1,15 +1,15 @@
|
|||||||
services:
|
services:
|
||||||
sranalysis:
|
sranalysis:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: sranalysis
|
container_name: sranalysis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3005:3000"
|
- "3005:3000"
|
||||||
networks:
|
networks:
|
||||||
- sranalysis-network
|
- sranalysis-network
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
sranalysis-network:
|
sranalysis-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
+13
-13
@@ -1,13 +1,13 @@
|
|||||||
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
||||||
import nextTypescript from "eslint-config-next/typescript";
|
import nextTypescript from "eslint-config-next/typescript";
|
||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, {
|
const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, {
|
||||||
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts"]
|
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts"]
|
||||||
}];
|
}];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
+10
-10
@@ -1,11 +1,11 @@
|
|||||||
import { getRequestConfig } from "next-intl/server";
|
import { getRequestConfig } from "next-intl/server";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
export default getRequestConfig( async () => {
|
export default getRequestConfig( async () => {
|
||||||
const locale = (await cookies()).get("MYNEXTAPP_LOCALE")?.value || "en";
|
const locale = (await cookies()).get("MYNEXTAPP_LOCALE")?.value || "en";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
messages: (await import(`../messages/${locale}.json`)).default
|
messages: (await import(`../messages/${locale}.json`)).default
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firefly 分析器",
|
"title": "Firefly 分析器",
|
||||||
"description": "Veritas 分析工具"
|
"description": "Veritas 分析工具"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "使用技能",
|
"useSkill": "使用技能",
|
||||||
"totalDamage": "总伤害",
|
"totalDamage": "总伤害",
|
||||||
"damagePerAV": "每行动值伤害",
|
"damagePerAV": "每行动值伤害",
|
||||||
"totalAV": "总行动值",
|
"totalAV": "总行动值",
|
||||||
"skillType": "技能类型",
|
"skillType": "技能类型",
|
||||||
"skillName": "技能名称",
|
"skillName": "技能名称",
|
||||||
"actionValue": "行动值",
|
"actionValue": "行动值",
|
||||||
"character": "角色",
|
"character": "角色",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"path": "命途",
|
"path": "命途",
|
||||||
"rarity": "稀有度",
|
"rarity": "稀有度",
|
||||||
"damageByActionValue": "此行动值的伤害",
|
"damageByActionValue": "此行动值的伤害",
|
||||||
"element": "属性",
|
"element": "属性",
|
||||||
"totalTurn": "总回合",
|
"totalTurn": "总回合",
|
||||||
"technique": "密技",
|
"technique": "密技",
|
||||||
"talent": "天赋",
|
"talent": "天赋",
|
||||||
"basic": "普攻",
|
"basic": "普攻",
|
||||||
"skill": "战技",
|
"skill": "战技",
|
||||||
"ultimate": "终结技",
|
"ultimate": "终结技",
|
||||||
"servant": "忆灵",
|
"servant": "忆灵",
|
||||||
"skillDamageBreakdown": "技能类别",
|
"skillDamageBreakdown": "技能类别",
|
||||||
"skillUsageDistribution": "技能使用分布",
|
"skillUsageDistribution": "技能使用分布",
|
||||||
"damageOverTime": "累计伤害",
|
"damageOverTime": "累计伤害",
|
||||||
"damage": "伤害",
|
"damage": "伤害",
|
||||||
"cumulativeDamage": "累计伤害量",
|
"cumulativeDamage": "累计伤害量",
|
||||||
"characterInformation": "角色信息",
|
"characterInformation": "角色信息",
|
||||||
"turnDetail": "轮次详情",
|
"turnDetail": "轮次详情",
|
||||||
"damageDetails": "伤害详情",
|
"damageDetails": "伤害详情",
|
||||||
"cycleCount": "轮次",
|
"cycleCount": "轮次",
|
||||||
"chartInfo": "图表信息",
|
"chartInfo": "图表信息",
|
||||||
"actionBar": "行动轴",
|
"actionBar": "行动轴",
|
||||||
"lineupInfo": "阵容信息",
|
"lineupInfo": "阵容信息",
|
||||||
"loadData": "加载战斗数据",
|
"loadData": "加载战斗数据",
|
||||||
"exportData": "导出战斗数据",
|
"exportData": "导出战斗数据",
|
||||||
"connectSetting": "连接设置",
|
"connectSetting": "连接设置",
|
||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
"unconnected": "未连接",
|
"unconnected": "未连接",
|
||||||
"socketConnection": "socket连接",
|
"socketConnection": "socket连接",
|
||||||
"connectionType": "连接类型",
|
"connectionType": "连接类型",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"connect": "连接",
|
"connect": "连接",
|
||||||
"checkGameConnect": "检查游戏连接",
|
"checkGameConnect": "检查游戏连接",
|
||||||
"other": "其他",
|
"other": "其他",
|
||||||
"host": "主机",
|
"host": "主机",
|
||||||
"port": "端口",
|
"port": "端口",
|
||||||
"hostPlaceHolder": "输入主机地址",
|
"hostPlaceHolder": "输入主机地址",
|
||||||
"portPlaceHolder": "输入端口号",
|
"portPlaceHolder": "输入端口号",
|
||||||
"noDamageDetail": "没有可用的伤害详情",
|
"noDamageDetail": "没有可用的伤害详情",
|
||||||
"noCharactersInLineup": "队伍中没有角色",
|
"noCharactersInLineup": "队伍中没有角色",
|
||||||
"noTurns": "尚未有回合",
|
"noTurns": "尚未有回合",
|
||||||
"type": "类型 ",
|
"type": "类型 ",
|
||||||
"warrior": "毁灭",
|
"warrior": "毁灭",
|
||||||
"knight": "存护",
|
"knight": "存护",
|
||||||
"mage": "智识",
|
"mage": "智识",
|
||||||
"priest": "丰饶",
|
"priest": "丰饶",
|
||||||
"rogue": "巡猎",
|
"rogue": "巡猎",
|
||||||
"shaman": "同协",
|
"shaman": "同协",
|
||||||
"warlock": "虚无",
|
"warlock": "虚无",
|
||||||
"memory": "记忆",
|
"memory": "记忆",
|
||||||
"elation": "欢愉",
|
"elation": "欢愉",
|
||||||
"fire": "火",
|
"fire": "火",
|
||||||
"ice": "冰",
|
"ice": "冰",
|
||||||
"imaginary": "虚数",
|
"imaginary": "虚数",
|
||||||
"physical": "物理",
|
"physical": "物理",
|
||||||
"quantum": "量子",
|
"quantum": "量子",
|
||||||
"thunder": "雷",
|
"thunder": "雷",
|
||||||
"wind": "风",
|
"wind": "风",
|
||||||
"cycle": "回合",
|
"cycle": "回合",
|
||||||
"wave": "波次",
|
"wave": "波次",
|
||||||
"hp": "生命值",
|
"hp": "生命值",
|
||||||
"atk": "攻击力",
|
"atk": "攻击力",
|
||||||
"speed": "速度",
|
"speed": "速度",
|
||||||
"critRate": "暴击率",
|
"critRate": "暴击率",
|
||||||
"critDmg": "暴击伤害",
|
"critDmg": "暴击伤害",
|
||||||
"breakEffect": "击破特攻",
|
"breakEffect": "击破特攻",
|
||||||
"effectRes": "效果抵抗",
|
"effectRes": "效果抵抗",
|
||||||
"energyRegenerationRate": "能量回复效率",
|
"energyRegenerationRate": "能量回复效率",
|
||||||
"effectHitRate": "效果命中",
|
"effectHitRate": "效果命中",
|
||||||
"outgoingHealingBoost": "治疗量加成",
|
"outgoingHealingBoost": "治疗量加成",
|
||||||
"fireDmgBoost": "火属性伤害提高",
|
"fireDmgBoost": "火属性伤害提高",
|
||||||
"iceDmgBoost": "冰属性伤害提高",
|
"iceDmgBoost": "冰属性伤害提高",
|
||||||
"imaginaryDmgBoost": "虚数属性伤害提高",
|
"imaginaryDmgBoost": "虚数属性伤害提高",
|
||||||
"physicalDmgBoost": "物理属性伤害提高",
|
"physicalDmgBoost": "物理属性伤害提高",
|
||||||
"quantumDmgBoost": "量子属性伤害提高",
|
"quantumDmgBoost": "量子属性伤害提高",
|
||||||
"thunderDmgBoost": "雷属性伤害提高",
|
"thunderDmgBoost": "雷属性伤害提高",
|
||||||
"windDmgBoost": "风属性伤害提高",
|
"windDmgBoost": "风属性伤害提高",
|
||||||
"pursued": "附加伤害",
|
"pursued": "附加伤害",
|
||||||
"true damage": "真实伤害",
|
"true damage": "真实伤害",
|
||||||
"elation damage": "欢愉伤害",
|
"elation damage": "欢愉伤害",
|
||||||
"follow-up": "追加攻击",
|
"follow-up": "追加攻击",
|
||||||
"elemental damage": "击破与超击破伤害",
|
"elemental damage": "击破与超击破伤害",
|
||||||
"dot": "持续伤害 ",
|
"dot": "持续伤害 ",
|
||||||
"damagePerCycle": "每轮伤害",
|
"damagePerCycle": "每轮伤害",
|
||||||
"damagePerCycleAndWave": "每轮/波伤害",
|
"damagePerCycleAndWave": "每轮/波伤害",
|
||||||
"damagePerWave": "每波伤害",
|
"damagePerWave": "每波伤害",
|
||||||
"lastTurn": "最后一回合",
|
"lastTurn": "最后一回合",
|
||||||
"qte": "QTE技能",
|
"qte": "QTE技能",
|
||||||
"mazenormal": "普通迷宫",
|
"mazenormal": "普通迷宫",
|
||||||
"level": "等级",
|
"level": "等级",
|
||||||
"relics": "遗器",
|
"relics": "遗器",
|
||||||
"eidolons": "星魂",
|
"eidolons": "星魂",
|
||||||
"lightcones": "光锥",
|
"lightcones": "光锥",
|
||||||
"trailblazer": "开拓者"
|
"trailblazer": "开拓者"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firely Analytics",
|
"title": "Firely Analytics",
|
||||||
"description": "Analytics tool for Veritas"
|
"description": "Analytics tool for Veritas"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "Use Ability",
|
"useSkill": "Use Ability",
|
||||||
"totalDamage": "Total damage",
|
"totalDamage": "Total damage",
|
||||||
"damagePerAV": "Damage/Action value",
|
"damagePerAV": "Damage/Action value",
|
||||||
"totalAV": "Total action value",
|
"totalAV": "Total action value",
|
||||||
"skillType": "Skill Type",
|
"skillType": "Skill Type",
|
||||||
"skillName": "Skill Name",
|
"skillName": "Skill Name",
|
||||||
"actionValue": "Action Value",
|
"actionValue": "Action Value",
|
||||||
"character": "Character",
|
"character": "Character",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
"path": "Path",
|
"path": "Path",
|
||||||
"rarity": "Rarity",
|
"rarity": "Rarity",
|
||||||
"element": "Element",
|
"element": "Element",
|
||||||
"totalTurn": "Total Turn",
|
"totalTurn": "Total Turn",
|
||||||
"technique": "Technique",
|
"technique": "Technique",
|
||||||
"talent": "Talent",
|
"talent": "Talent",
|
||||||
"basic": "Basic Attack",
|
"basic": "Basic Attack",
|
||||||
"skill": "Skill",
|
"skill": "Skill",
|
||||||
"ultimate": "Ultimate",
|
"ultimate": "Ultimate",
|
||||||
"servant": "Servant",
|
"servant": "Servant",
|
||||||
"skillDamageBreakdown": "Skill Damage Breakdown",
|
"skillDamageBreakdown": "Skill Damage Breakdown",
|
||||||
"skillUsageDistribution": "Skill Usage Distribution",
|
"skillUsageDistribution": "Skill Usage Distribution",
|
||||||
"damageOverTime": "Damage Over Time",
|
"damageOverTime": "Damage Over Time",
|
||||||
"damage": "Damage",
|
"damage": "Damage",
|
||||||
"cumulativeDamage": "Cumulative Damage",
|
"cumulativeDamage": "Cumulative Damage",
|
||||||
"damageByActionValue": "Damage by Action Value",
|
"damageByActionValue": "Damage by Action Value",
|
||||||
"characterInformation": "Character Information",
|
"characterInformation": "Character Information",
|
||||||
"turnDetail": "Turn Details",
|
"turnDetail": "Turn Details",
|
||||||
"damageDetails": "Damage Details",
|
"damageDetails": "Damage Details",
|
||||||
"cycleCount": "Cycle Count",
|
"cycleCount": "Cycle Count",
|
||||||
"chartInfo": "Chart info",
|
"chartInfo": "Chart info",
|
||||||
"actionBar": "Action time line",
|
"actionBar": "Action time line",
|
||||||
"lineupInfo": "Lineup info",
|
"lineupInfo": "Lineup info",
|
||||||
"loadData": "Load data battle",
|
"loadData": "Load data battle",
|
||||||
"exportData": "Export data battle",
|
"exportData": "Export data battle",
|
||||||
"connectSetting": "Connection Setting",
|
"connectSetting": "Connection Setting",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"unconnected": "Unconnected",
|
"unconnected": "Unconnected",
|
||||||
"socketConnection": "Socket Connection",
|
"socketConnection": "Socket Connection",
|
||||||
"connectionType": "Connection Type",
|
"connectionType": "Connection Type",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"connect": "Connect",
|
"connect": "Connect",
|
||||||
"checkGameConnect": "Check game connection",
|
"checkGameConnect": "Check game connection",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"hostPlaceHolder": "Enter Host",
|
"hostPlaceHolder": "Enter Host",
|
||||||
"portPlaceHolder": "Enter port number",
|
"portPlaceHolder": "Enter port number",
|
||||||
"noDamageDetail": "No damage details available",
|
"noDamageDetail": "No damage details available",
|
||||||
"noCharactersInLineup": "No characters in lineup",
|
"noCharactersInLineup": "No characters in lineup",
|
||||||
"noTurns": "No turns yet",
|
"noTurns": "No turns yet",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"warrior": "The Destruction",
|
"warrior": "The Destruction",
|
||||||
"knight": "The Preservation",
|
"knight": "The Preservation",
|
||||||
"mage": "The Erudition",
|
"mage": "The Erudition",
|
||||||
"priest": "The Abundance",
|
"priest": "The Abundance",
|
||||||
"rogue": "The Hunt",
|
"rogue": "The Hunt",
|
||||||
"shaman": "The Harmony",
|
"shaman": "The Harmony",
|
||||||
"warlock": "The Nihility",
|
"warlock": "The Nihility",
|
||||||
"memory": "The Remembrance",
|
"memory": "The Remembrance",
|
||||||
"elation": "The Elation",
|
"elation": "The Elation",
|
||||||
"fire": "Fire",
|
"fire": "Fire",
|
||||||
"ice": "Ice",
|
"ice": "Ice",
|
||||||
"imaginary": "Imaginary",
|
"imaginary": "Imaginary",
|
||||||
"physical": "Physical",
|
"physical": "Physical",
|
||||||
"quantum": "Quantum",
|
"quantum": "Quantum",
|
||||||
"thunder": "Thunder",
|
"thunder": "Thunder",
|
||||||
"wind": "Wind",
|
"wind": "Wind",
|
||||||
"cycle": "Cycle",
|
"cycle": "Cycle",
|
||||||
"wave": "Wave",
|
"wave": "Wave",
|
||||||
"hp": "Hp",
|
"hp": "Hp",
|
||||||
"atk": "Atk",
|
"atk": "Atk",
|
||||||
"speed": "Speed",
|
"speed": "Speed",
|
||||||
"critRate": "Crit Rate",
|
"critRate": "Crit Rate",
|
||||||
"critDmg": "Crit Dmg",
|
"critDmg": "Crit Dmg",
|
||||||
"breakEffect": "Break Effect",
|
"breakEffect": "Break Effect",
|
||||||
"effectRes": "Effect Res",
|
"effectRes": "Effect Res",
|
||||||
"energyRegenerationRate": "Energy Regeneration Rate",
|
"energyRegenerationRate": "Energy Regeneration Rate",
|
||||||
"effectHitRate": "Effect Hit Rate",
|
"effectHitRate": "Effect Hit Rate",
|
||||||
"outgoingHealingBoost": "Outgoing Healing Boost",
|
"outgoingHealingBoost": "Outgoing Healing Boost",
|
||||||
"fireDmgBoost": "Fire damage boost",
|
"fireDmgBoost": "Fire damage boost",
|
||||||
"iceDmgBoost": "Ice damage Boost",
|
"iceDmgBoost": "Ice damage Boost",
|
||||||
"imaginaryDmgBoost": "Imaginary damage boost",
|
"imaginaryDmgBoost": "Imaginary damage boost",
|
||||||
"physicalDmgBoost": "Physical damage boost",
|
"physicalDmgBoost": "Physical damage boost",
|
||||||
"quantumDmgBoost": "Quantum damage boost",
|
"quantumDmgBoost": "Quantum damage boost",
|
||||||
"thunderDmgBoost": "Thunder damage boost",
|
"thunderDmgBoost": "Thunder damage boost",
|
||||||
"windDmgBoost": "Wind damage boost",
|
"windDmgBoost": "Wind damage boost",
|
||||||
"pursued": "Additional damage",
|
"pursued": "Additional damage",
|
||||||
"true damage": "True damage",
|
"true damage": "True damage",
|
||||||
"elation damage": "Elation Damage",
|
"elation damage": "Elation Damage",
|
||||||
"follow-up": "Follow-up Damage",
|
"follow-up": "Follow-up Damage",
|
||||||
"elemental damage": "Break and Super break damage",
|
"elemental damage": "Break and Super break damage",
|
||||||
"dot": "Damage over time ",
|
"dot": "Damage over time ",
|
||||||
"damagePerCycle": "Damage per Cycle",
|
"damagePerCycle": "Damage per Cycle",
|
||||||
"damagePerCycleAndWave": "Damage per Cycle and Wave",
|
"damagePerCycleAndWave": "Damage per Cycle and Wave",
|
||||||
"damagePerWave": "Damage per Wave",
|
"damagePerWave": "Damage per Wave",
|
||||||
"lastTurn": "Last turn",
|
"lastTurn": "Last turn",
|
||||||
"qte": "QTE Skill",
|
"qte": "QTE Skill",
|
||||||
"mazenormal": "MazeNormal",
|
"mazenormal": "MazeNormal",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"relics": "Relics",
|
"relics": "Relics",
|
||||||
"eidolons": "Eidolons",
|
"eidolons": "Eidolons",
|
||||||
"lightcones": "Lightcones",
|
"lightcones": "Lightcones",
|
||||||
"trailblazer": "Trailblazer"
|
"trailblazer": "Trailblazer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firefly Analytics",
|
"title": "Firefly Analytics",
|
||||||
"description": "Veritasの分析ツール"
|
"description": "Veritasの分析ツール"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "スキルを使用",
|
"useSkill": "スキルを使用",
|
||||||
"totalDamage": "総ダメージ",
|
"totalDamage": "総ダメージ",
|
||||||
"damagePerAV": "ダメージ/行動値",
|
"damagePerAV": "ダメージ/行動値",
|
||||||
"totalAV": "総行動値",
|
"totalAV": "総行動値",
|
||||||
"skillType": "スキルタイプ",
|
"skillType": "スキルタイプ",
|
||||||
"skillName": "スキル名",
|
"skillName": "スキル名",
|
||||||
"actionValue": "行動値",
|
"actionValue": "行動値",
|
||||||
"character": "キャラクター",
|
"character": "キャラクター",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"path": "運命",
|
"path": "運命",
|
||||||
"rarity": "レアリティ",
|
"rarity": "レアリティ",
|
||||||
"element": "属性",
|
"element": "属性",
|
||||||
"totalTurn": "総ターン数",
|
"totalTurn": "総ターン数",
|
||||||
"technique": "秘技",
|
"technique": "秘技",
|
||||||
"talent": "天賦",
|
"talent": "天賦",
|
||||||
"basic": "通常攻撃",
|
"basic": "通常攻撃",
|
||||||
"skill": "スキル",
|
"skill": "スキル",
|
||||||
"ultimate": "必殺技",
|
"ultimate": "必殺技",
|
||||||
"servant": "召喚",
|
"servant": "召喚",
|
||||||
"skillDamageBreakdown": "スキルダメージ内訳",
|
"skillDamageBreakdown": "スキルダメージ内訳",
|
||||||
"skillUsageDistribution": "スキル使用分布",
|
"skillUsageDistribution": "スキル使用分布",
|
||||||
"damageOverTime": "継続ダメージ",
|
"damageOverTime": "継続ダメージ",
|
||||||
"damage": "ダメージ",
|
"damage": "ダメージ",
|
||||||
"cumulativeDamage": "累積ダメージ",
|
"cumulativeDamage": "累積ダメージ",
|
||||||
"damageByActionValue": "行動値別ダメージ",
|
"damageByActionValue": "行動値別ダメージ",
|
||||||
"characterInformation": "キャラクター情報",
|
"characterInformation": "キャラクター情報",
|
||||||
"turnDetail": "ターン詳細",
|
"turnDetail": "ターン詳細",
|
||||||
"damageDetails": "ダメージ詳細",
|
"damageDetails": "ダメージ詳細",
|
||||||
"cycleCount": "サイクル数",
|
"cycleCount": "サイクル数",
|
||||||
"chartInfo": "チャート情報",
|
"chartInfo": "チャート情報",
|
||||||
"actionBar": "アクションタイムライン",
|
"actionBar": "アクションタイムライン",
|
||||||
"lineupInfo": "編成情報",
|
"lineupInfo": "編成情報",
|
||||||
"loadData": "戦闘データを読み込む",
|
"loadData": "戦闘データを読み込む",
|
||||||
"exportData": "戦闘データをエクスポート",
|
"exportData": "戦闘データをエクスポート",
|
||||||
"connectSetting": "接続設定",
|
"connectSetting": "接続設定",
|
||||||
"connected": "接続済み",
|
"connected": "接続済み",
|
||||||
"unconnected": "未接続",
|
"unconnected": "未接続",
|
||||||
"socketConnection": "ソケット接続",
|
"socketConnection": "ソケット接続",
|
||||||
"connectionType": "接続タイプ",
|
"connectionType": "接続タイプ",
|
||||||
"status": "ステータス",
|
"status": "ステータス",
|
||||||
"connect": "接続する",
|
"connect": "接続する",
|
||||||
"checkGameConnect": "ゲーム接続を確認",
|
"checkGameConnect": "ゲーム接続を確認",
|
||||||
"other": "その他",
|
"other": "その他",
|
||||||
"host": "ホスト",
|
"host": "ホスト",
|
||||||
"port": "ポート",
|
"port": "ポート",
|
||||||
"hostPlaceHolder": "ホストを入力",
|
"hostPlaceHolder": "ホストを入力",
|
||||||
"portPlaceHolder": "ポート番号を入力",
|
"portPlaceHolder": "ポート番号を入力",
|
||||||
"noDamageDetail": "ダメージ詳細がありません",
|
"noDamageDetail": "ダメージ詳細がありません",
|
||||||
"noCharactersInLineup": "編成にキャラクターがいません",
|
"noCharactersInLineup": "編成にキャラクターがいません",
|
||||||
"noTurns": "まだターンがありません",
|
"noTurns": "まだターンがありません",
|
||||||
"type": "タイプ",
|
"type": "タイプ",
|
||||||
"warrior": "壊滅",
|
"warrior": "壊滅",
|
||||||
"knight": "存護",
|
"knight": "存護",
|
||||||
"mage": "知恵",
|
"mage": "知恵",
|
||||||
"priest": "豊穣",
|
"priest": "豊穣",
|
||||||
"rogue": "巡狩",
|
"rogue": "巡狩",
|
||||||
"shaman": "調和",
|
"shaman": "調和",
|
||||||
"warlock": "虚無",
|
"warlock": "虚無",
|
||||||
"memory": "記憶",
|
"memory": "記憶",
|
||||||
"elation": "愉悦",
|
"elation": "愉悦",
|
||||||
"fire": "炎",
|
"fire": "炎",
|
||||||
"ice": "氷",
|
"ice": "氷",
|
||||||
"imaginary": "虚数",
|
"imaginary": "虚数",
|
||||||
"physical": "物理",
|
"physical": "物理",
|
||||||
"quantum": "量子",
|
"quantum": "量子",
|
||||||
"thunder": "雷",
|
"thunder": "雷",
|
||||||
"wind": "風",
|
"wind": "風",
|
||||||
"cycle": "サイクル",
|
"cycle": "サイクル",
|
||||||
"wave": "ウェーブ",
|
"wave": "ウェーブ",
|
||||||
"hp": "HP",
|
"hp": "HP",
|
||||||
"atk": "攻撃力",
|
"atk": "攻撃力",
|
||||||
"speed": "速度",
|
"speed": "速度",
|
||||||
"critRate": "会心率",
|
"critRate": "会心率",
|
||||||
"critDmg": "会心ダメージ",
|
"critDmg": "会心ダメージ",
|
||||||
"breakEffect": "撃破特効",
|
"breakEffect": "撃破特効",
|
||||||
"effectRes": "効果抵抗",
|
"effectRes": "効果抵抗",
|
||||||
"energyRegenerationRate": "エネルギー回復効率",
|
"energyRegenerationRate": "エネルギー回復効率",
|
||||||
"effectHitRate": "効果命中率",
|
"effectHitRate": "効果命中率",
|
||||||
"outgoingHealingBoost": "治癒量上昇",
|
"outgoingHealingBoost": "治癒量上昇",
|
||||||
"fireDmgBoost": "炎属性ダメージ上昇",
|
"fireDmgBoost": "炎属性ダメージ上昇",
|
||||||
"iceDmgBoost": "氷属性ダメージ上昇",
|
"iceDmgBoost": "氷属性ダメージ上昇",
|
||||||
"imaginaryDmgBoost": "虚数属性ダメージ上昇",
|
"imaginaryDmgBoost": "虚数属性ダメージ上昇",
|
||||||
"physicalDmgBoost": "物理属性ダメージ上昇",
|
"physicalDmgBoost": "物理属性ダメージ上昇",
|
||||||
"quantumDmgBoost": "量子属性ダメージ上昇",
|
"quantumDmgBoost": "量子属性ダメージ上昇",
|
||||||
"thunderDmgBoost": "雷属性ダメージ上昇",
|
"thunderDmgBoost": "雷属性ダメージ上昇",
|
||||||
"windDmgBoost": "風属性ダメージ上昇",
|
"windDmgBoost": "風属性ダメージ上昇",
|
||||||
"pursued": "追加ダメージ",
|
"pursued": "追加ダメージ",
|
||||||
"true damage": "貫通ダメージ",
|
"true damage": "貫通ダメージ",
|
||||||
"elation damage": "愉悦ダメージ",
|
"elation damage": "愉悦ダメージ",
|
||||||
"follow-up": "追撃ダメージ",
|
"follow-up": "追撃ダメージ",
|
||||||
"elemental damage": "撃破・超撃破ダメージ",
|
"elemental damage": "撃破・超撃破ダメージ",
|
||||||
"dot": "継続ダメージ",
|
"dot": "継続ダメージ",
|
||||||
"damagePerCycle": "サイクル毎のダメージ",
|
"damagePerCycle": "サイクル毎のダメージ",
|
||||||
"damagePerCycleAndWave": "サイクル・ウェーブ毎のダメージ",
|
"damagePerCycleAndWave": "サイクル・ウェーブ毎のダメージ",
|
||||||
"damagePerWave": "ウェーブ毎のダメージ",
|
"damagePerWave": "ウェーブ毎のダメージ",
|
||||||
"lastTurn": "最後のターン",
|
"lastTurn": "最後のターン",
|
||||||
"qte": "QTEスキル",
|
"qte": "QTEスキル",
|
||||||
"mazenormal": "通常迷宮",
|
"mazenormal": "通常迷宮",
|
||||||
"level": "レベル",
|
"level": "レベル",
|
||||||
"relics": "遺物",
|
"relics": "遺物",
|
||||||
"eidolons": "星魂",
|
"eidolons": "星魂",
|
||||||
"lightcones": "光円錐",
|
"lightcones": "光円錐",
|
||||||
"trailblazer": "開拓者"
|
"trailblazer": "開拓者"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firefly 분석",
|
"title": "Firefly 분석",
|
||||||
"description": "Veritas의 분석 도구"
|
"description": "Veritas의 분석 도구"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "스킬 사용",
|
"useSkill": "스킬 사용",
|
||||||
"totalDamage": "총 피해량",
|
"totalDamage": "총 피해량",
|
||||||
"damagePerAV": "피해량/행동값",
|
"damagePerAV": "피해량/행동값",
|
||||||
"totalAV": "총 행동값",
|
"totalAV": "총 행동값",
|
||||||
"skillType": "스킬 유형",
|
"skillType": "스킬 유형",
|
||||||
"skillName": "스킬 이름",
|
"skillName": "스킬 이름",
|
||||||
"actionValue": "행동값",
|
"actionValue": "행동값",
|
||||||
"character": "캐릭터",
|
"character": "캐릭터",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"path": "운명",
|
"path": "운명",
|
||||||
"rarity": "희귀도",
|
"rarity": "희귀도",
|
||||||
"element": "속성",
|
"element": "속성",
|
||||||
"totalTurn": "총 턴 수",
|
"totalTurn": "총 턴 수",
|
||||||
"technique": "비전",
|
"technique": "비전",
|
||||||
"talent": "재능",
|
"talent": "재능",
|
||||||
"basic": "기본 공격",
|
"basic": "기본 공격",
|
||||||
"skill": "스킬",
|
"skill": "스킬",
|
||||||
"ultimate": "필살기",
|
"ultimate": "필살기",
|
||||||
"servant": "소환",
|
"servant": "소환",
|
||||||
"skillDamageBreakdown": "스킬 피해 세부사항",
|
"skillDamageBreakdown": "스킬 피해 세부사항",
|
||||||
"skillUsageDistribution": "스킬 사용 분포",
|
"skillUsageDistribution": "스킬 사용 분포",
|
||||||
"damageOverTime": "시간당 피해",
|
"damageOverTime": "시간당 피해",
|
||||||
"damage": "피해",
|
"damage": "피해",
|
||||||
"cumulativeDamage": "누적 피해",
|
"cumulativeDamage": "누적 피해",
|
||||||
"damageByActionValue": "행동값별 피해",
|
"damageByActionValue": "행동값별 피해",
|
||||||
"characterInformation": "캐릭터 정보",
|
"characterInformation": "캐릭터 정보",
|
||||||
"turnDetail": "턴 상세 정보",
|
"turnDetail": "턴 상세 정보",
|
||||||
"damageDetails": "피해 세부사항",
|
"damageDetails": "피해 세부사항",
|
||||||
"cycleCount": "사이클 수",
|
"cycleCount": "사이클 수",
|
||||||
"chartInfo": "차트 정보",
|
"chartInfo": "차트 정보",
|
||||||
"actionBar": "행동 타임라인",
|
"actionBar": "행동 타임라인",
|
||||||
"lineupInfo": "편성 정보",
|
"lineupInfo": "편성 정보",
|
||||||
"loadData": "전투 데이터 불러오기",
|
"loadData": "전투 데이터 불러오기",
|
||||||
"exportData": "전투 데이터 내보내기",
|
"exportData": "전투 데이터 내보내기",
|
||||||
"connectSetting": "연결 설정",
|
"connectSetting": "연결 설정",
|
||||||
"connected": "연결됨",
|
"connected": "연결됨",
|
||||||
"unconnected": "연결되지 않음",
|
"unconnected": "연결되지 않음",
|
||||||
"socketConnection": "소켓 연결",
|
"socketConnection": "소켓 연결",
|
||||||
"connectionType": "연결 유형",
|
"connectionType": "연결 유형",
|
||||||
"status": "상태",
|
"status": "상태",
|
||||||
"connect": "연결",
|
"connect": "연결",
|
||||||
"checkGameConnect": "게임 연결 확인",
|
"checkGameConnect": "게임 연결 확인",
|
||||||
"other": "기타",
|
"other": "기타",
|
||||||
"host": "호스트",
|
"host": "호스트",
|
||||||
"port": "포트",
|
"port": "포트",
|
||||||
"hostPlaceHolder": "호스트 입력",
|
"hostPlaceHolder": "호스트 입력",
|
||||||
"portPlaceHolder": "포트 번호 입력",
|
"portPlaceHolder": "포트 번호 입력",
|
||||||
"noDamageDetail": "피해 세부사항 없음",
|
"noDamageDetail": "피해 세부사항 없음",
|
||||||
"noCharactersInLineup": "편성에 캐릭터 없음",
|
"noCharactersInLineup": "편성에 캐릭터 없음",
|
||||||
"noTurns": "아직 턴이 없습니다",
|
"noTurns": "아직 턴이 없습니다",
|
||||||
"type": "유형",
|
"type": "유형",
|
||||||
"warrior": "파괴",
|
"warrior": "파괴",
|
||||||
"knight": "보존",
|
"knight": "보존",
|
||||||
"mage": "지혜",
|
"mage": "지혜",
|
||||||
"priest": "풍요",
|
"priest": "풍요",
|
||||||
"rogue": "사냥",
|
"rogue": "사냥",
|
||||||
"shaman": "조화",
|
"shaman": "조화",
|
||||||
"warlock": "허무",
|
"warlock": "허무",
|
||||||
"memory": "기억",
|
"memory": "기억",
|
||||||
"elation": "환락",
|
"elation": "환락",
|
||||||
"fire": "불",
|
"fire": "불",
|
||||||
"ice": "얼음",
|
"ice": "얼음",
|
||||||
"imaginary": "허수",
|
"imaginary": "허수",
|
||||||
"physical": "물리",
|
"physical": "물리",
|
||||||
"quantum": "양자",
|
"quantum": "양자",
|
||||||
"thunder": "번개",
|
"thunder": "번개",
|
||||||
"wind": "바람",
|
"wind": "바람",
|
||||||
"cycle": "사이클",
|
"cycle": "사이클",
|
||||||
"wave": "웨이브",
|
"wave": "웨이브",
|
||||||
"hp": "HP",
|
"hp": "HP",
|
||||||
"atk": "공격력",
|
"atk": "공격력",
|
||||||
"speed": "속도",
|
"speed": "속도",
|
||||||
"critRate": "치명타 확률",
|
"critRate": "치명타 확률",
|
||||||
"critDmg": "치명타 피해",
|
"critDmg": "치명타 피해",
|
||||||
"breakEffect": "파괴 효과",
|
"breakEffect": "파괴 효과",
|
||||||
"effectRes": "효과 저항",
|
"effectRes": "효과 저항",
|
||||||
"energyRegenerationRate": "에너지 회복 속도",
|
"energyRegenerationRate": "에너지 회복 속도",
|
||||||
"effectHitRate": "효과 적중률",
|
"effectHitRate": "효과 적중률",
|
||||||
"outgoingHealingBoost": "치유량 증가",
|
"outgoingHealingBoost": "치유량 증가",
|
||||||
"fireDmgBoost": "불 속성 피해 증가",
|
"fireDmgBoost": "불 속성 피해 증가",
|
||||||
"iceDmgBoost": "얼음 속성 피해 증가",
|
"iceDmgBoost": "얼음 속성 피해 증가",
|
||||||
"imaginaryDmgBoost": "허수 속성 피해 증가",
|
"imaginaryDmgBoost": "허수 속성 피해 증가",
|
||||||
"physicalDmgBoost": "물리 속성 피해 증가",
|
"physicalDmgBoost": "물리 속성 피해 증가",
|
||||||
"quantumDmgBoost": "양자 속성 피해 증가",
|
"quantumDmgBoost": "양자 속성 피해 증가",
|
||||||
"thunderDmgBoost": "번개 속성 피해 증가",
|
"thunderDmgBoost": "번개 속성 피해 증가",
|
||||||
"windDmgBoost": "바람 속성 피해 증가",
|
"windDmgBoost": "바람 속성 피해 증가",
|
||||||
"pursued": "추가 피해",
|
"pursued": "추가 피해",
|
||||||
"true damage": "관통 피해",
|
"true damage": "관통 피해",
|
||||||
"elation damage": "환락 피해",
|
"elation damage": "환락 피해",
|
||||||
"follow-up": "추가 공격 피해",
|
"follow-up": "추가 공격 피해",
|
||||||
"elemental damage": "파괴 및 초파괴 피해",
|
"elemental damage": "파괴 및 초파괴 피해",
|
||||||
"dot": "지속 피해",
|
"dot": "지속 피해",
|
||||||
"damagePerCycle": "사이클당 피해",
|
"damagePerCycle": "사이클당 피해",
|
||||||
"damagePerCycleAndWave": "사이클 및 웨이브당 피해",
|
"damagePerCycleAndWave": "사이클 및 웨이브당 피해",
|
||||||
"damagePerWave": "웨이브당 피해",
|
"damagePerWave": "웨이브당 피해",
|
||||||
"lastTurn": "마지막 턴",
|
"lastTurn": "마지막 턴",
|
||||||
"qte": "QTE 스킬",
|
"qte": "QTE 스킬",
|
||||||
"mazenormal": "일반 미궁",
|
"mazenormal": "일반 미궁",
|
||||||
"level": "레벨",
|
"level": "레벨",
|
||||||
"relics": "유물",
|
"relics": "유물",
|
||||||
"eidolons": "에이돌론",
|
"eidolons": "에이돌론",
|
||||||
"lightcones": "광추",
|
"lightcones": "광추",
|
||||||
"trailblazer": "개척자"
|
"trailblazer": "개척자"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firefly Analytics",
|
"title": "Firefly Analytics",
|
||||||
"description": "Công cụ phân tích cho Veritas"
|
"description": "Công cụ phân tích cho Veritas"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "Sử dụng kỹ năng",
|
"useSkill": "Sử dụng kỹ năng",
|
||||||
"totalDamage": "Tổng sát thương",
|
"totalDamage": "Tổng sát thương",
|
||||||
"damagePerAV": "Sát thương/giá trị hành động",
|
"damagePerAV": "Sát thương/giá trị hành động",
|
||||||
"totalAV": "Tổng giá trị hành động",
|
"totalAV": "Tổng giá trị hành động",
|
||||||
"skillType": "Loại kỹ năng",
|
"skillType": "Loại kỹ năng",
|
||||||
"skillName": "Tên kỹ năng",
|
"skillName": "Tên kỹ năng",
|
||||||
"actionValue": "Giá trị hành động",
|
"actionValue": "Giá trị hành động",
|
||||||
"character": "Nhân vật",
|
"character": "Nhân vật",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"path": "Vận mệnh",
|
"path": "Vận mệnh",
|
||||||
"rarity": "Số sao",
|
"rarity": "Số sao",
|
||||||
"element": "Nguyên tố",
|
"element": "Nguyên tố",
|
||||||
"totalTurn": "Tổng lượt",
|
"totalTurn": "Tổng lượt",
|
||||||
"damageByActionValue": "Sát thương theo giá trị hành động",
|
"damageByActionValue": "Sát thương theo giá trị hành động",
|
||||||
"technique": "Bĩ kỹ",
|
"technique": "Bĩ kỹ",
|
||||||
"talent": "Thiên phú",
|
"talent": "Thiên phú",
|
||||||
"basic": "Tấn công thường",
|
"basic": "Tấn công thường",
|
||||||
"skill": "Chiến kỹ",
|
"skill": "Chiến kỹ",
|
||||||
"ultimate": "Tuyệt Kĩ",
|
"ultimate": "Tuyệt Kĩ",
|
||||||
"servant": "Vật triệu hồi",
|
"servant": "Vật triệu hồi",
|
||||||
"skillDamageBreakdown": "Phân tích sát thương kỹ năng",
|
"skillDamageBreakdown": "Phân tích sát thương kỹ năng",
|
||||||
"skillUsageDistribution": "Phân phối sử dụng kỹ năng",
|
"skillUsageDistribution": "Phân phối sử dụng kỹ năng",
|
||||||
"damageOverTime": "Sát thương theo thời gian",
|
"damageOverTime": "Sát thương theo thời gian",
|
||||||
"damage": "Sát thương",
|
"damage": "Sát thương",
|
||||||
"cumulativeDamage": "Sát thương tích lũy",
|
"cumulativeDamage": "Sát thương tích lũy",
|
||||||
"characterInformation": "Thông tin nhân vật",
|
"characterInformation": "Thông tin nhân vật",
|
||||||
"turnDetail": "Chi tiết lượt",
|
"turnDetail": "Chi tiết lượt",
|
||||||
"damageDetails": "Chi tiết sát thương",
|
"damageDetails": "Chi tiết sát thương",
|
||||||
"cycleCount": "Số vòng",
|
"cycleCount": "Số vòng",
|
||||||
"chartInfo": "Thông tin biểu đồ",
|
"chartInfo": "Thông tin biểu đồ",
|
||||||
"actionBar": "Thanh hành động",
|
"actionBar": "Thanh hành động",
|
||||||
"lineupInfo": "Thông tin đội hình",
|
"lineupInfo": "Thông tin đội hình",
|
||||||
"loadData": "Tải dữ liệu trận đấu",
|
"loadData": "Tải dữ liệu trận đấu",
|
||||||
"exportData": "Xuất dữ liệu trận đấu",
|
"exportData": "Xuất dữ liệu trận đấu",
|
||||||
"connectSetting": "Cài đặt kết nối",
|
"connectSetting": "Cài đặt kết nối",
|
||||||
"connected": "Đã kết nối",
|
"connected": "Đã kết nối",
|
||||||
"unconnected": "Chưa kết nối",
|
"unconnected": "Chưa kết nối",
|
||||||
"socketConnection": "Kết nối socket",
|
"socketConnection": "Kết nối socket",
|
||||||
"connectionType": "Loại kết nối",
|
"connectionType": "Loại kết nối",
|
||||||
"status": "Trạng thái",
|
"status": "Trạng thái",
|
||||||
"connect": "Kết nối",
|
"connect": "Kết nối",
|
||||||
"checkGameConnect": "Kiểm tra kết nối game",
|
"checkGameConnect": "Kiểm tra kết nối game",
|
||||||
"other": "Khác",
|
"other": "Khác",
|
||||||
"host": "Địa chỉ kết nối",
|
"host": "Địa chỉ kết nối",
|
||||||
"port": "Cổng kết nối",
|
"port": "Cổng kết nối",
|
||||||
"hostPlaceHolder": "Nhập địa chỉ kết nối",
|
"hostPlaceHolder": "Nhập địa chỉ kết nối",
|
||||||
"portPlaceHolder": "Nhâp cổng kết nối",
|
"portPlaceHolder": "Nhâp cổng kết nối",
|
||||||
"noDamageDetail": "Không có chi tiết sát thương",
|
"noDamageDetail": "Không có chi tiết sát thương",
|
||||||
"noCharactersInLineup": "Không có nhân vật trong đội hình",
|
"noCharactersInLineup": "Không có nhân vật trong đội hình",
|
||||||
"noTurns": "Chưa có lượt hành động",
|
"noTurns": "Chưa có lượt hành động",
|
||||||
"type": "Loại",
|
"type": "Loại",
|
||||||
"warrior": "Hủy Diệt",
|
"warrior": "Hủy Diệt",
|
||||||
"knight": "Bảo Hộ",
|
"knight": "Bảo Hộ",
|
||||||
"mage": "Tri Thức",
|
"mage": "Tri Thức",
|
||||||
"priest": "Phong Phú",
|
"priest": "Phong Phú",
|
||||||
"rogue": "Săn Bắn",
|
"rogue": "Săn Bắn",
|
||||||
"shaman": "Hài Hòa",
|
"shaman": "Hài Hòa",
|
||||||
"warlock": "Hư Vô",
|
"warlock": "Hư Vô",
|
||||||
"memory": "Ký Ức",
|
"memory": "Ký Ức",
|
||||||
"elation": "Vui vẻ",
|
"elation": "Vui vẻ",
|
||||||
"fire": "Lửa",
|
"fire": "Lửa",
|
||||||
"ice": "Băng",
|
"ice": "Băng",
|
||||||
"imaginary": "Số ảo",
|
"imaginary": "Số ảo",
|
||||||
"physical": "Vật Lý",
|
"physical": "Vật Lý",
|
||||||
"quantum": "Lượng Tử",
|
"quantum": "Lượng Tử",
|
||||||
"thunder": "Lôi",
|
"thunder": "Lôi",
|
||||||
"wind": "Phong",
|
"wind": "Phong",
|
||||||
"cycle": "Vòng",
|
"cycle": "Vòng",
|
||||||
"wave": "Đợt quái",
|
"wave": "Đợt quái",
|
||||||
"hp": "Máu",
|
"hp": "Máu",
|
||||||
"atk": "Tấn công",
|
"atk": "Tấn công",
|
||||||
"speed": "Tốc độ",
|
"speed": "Tốc độ",
|
||||||
"critRate": "Tỉ lệ chí mạng",
|
"critRate": "Tỉ lệ chí mạng",
|
||||||
"critDmg": "Sát thương chí mạng",
|
"critDmg": "Sát thương chí mạng",
|
||||||
"breakEffect": "Tấn công kích phá",
|
"breakEffect": "Tấn công kích phá",
|
||||||
"effectRes": "Kháng hiệu ứng",
|
"effectRes": "Kháng hiệu ứng",
|
||||||
"energyRegenerationRate": "Hiệu quả nạp",
|
"energyRegenerationRate": "Hiệu quả nạp",
|
||||||
"effectHitRate": "Chinh xác hiệu ứng",
|
"effectHitRate": "Chinh xác hiệu ứng",
|
||||||
"outgoingHealingBoost": "Hiệu quả trị liệu",
|
"outgoingHealingBoost": "Hiệu quả trị liệu",
|
||||||
"fireDmgBoost": "Tăng sát thương hỏa",
|
"fireDmgBoost": "Tăng sát thương hỏa",
|
||||||
"iceDmgBoost": "Tăng sát thương băng",
|
"iceDmgBoost": "Tăng sát thương băng",
|
||||||
"imaginaryDmgBoost": "Tăng sát thương số ảo",
|
"imaginaryDmgBoost": "Tăng sát thương số ảo",
|
||||||
"physicalDmgBoost": "Tăng sát thương vật lý",
|
"physicalDmgBoost": "Tăng sát thương vật lý",
|
||||||
"quantumDmgBoost": "Tăng sát thương lượng tử",
|
"quantumDmgBoost": "Tăng sát thương lượng tử",
|
||||||
"thunderDmgBoost": "Tăng sát thương lôi",
|
"thunderDmgBoost": "Tăng sát thương lôi",
|
||||||
"windDmgBoost": "Tăng sát thương phong",
|
"windDmgBoost": "Tăng sát thương phong",
|
||||||
"pursued": "Sát thương kèm theo",
|
"pursued": "Sát thương kèm theo",
|
||||||
"true damage": "Sát thương chuẩn",
|
"true damage": "Sát thương chuẩn",
|
||||||
"elation damage": "Sát thương vui vẻ",
|
"elation damage": "Sát thương vui vẻ",
|
||||||
"follow-up": "Sát thương theo sau",
|
"follow-up": "Sát thương theo sau",
|
||||||
"elemental damage": "Sát thương phá vỡ và siêu phá vỡ",
|
"elemental damage": "Sát thương phá vỡ và siêu phá vỡ",
|
||||||
"dot": "Sát Thương Duy Trì",
|
"dot": "Sát Thương Duy Trì",
|
||||||
"damagePerCycle": "Sát thương mỗi vòng",
|
"damagePerCycle": "Sát thương mỗi vòng",
|
||||||
"damagePerCycleAndWave": "Sát thương mỗi vòng và đợt",
|
"damagePerCycleAndWave": "Sát thương mỗi vòng và đợt",
|
||||||
"damagePerWave": "Sát thương mỗi đợt",
|
"damagePerWave": "Sát thương mỗi đợt",
|
||||||
"lastTurn": "Lượt cuối",
|
"lastTurn": "Lượt cuối",
|
||||||
"qte": "Kỹ năng QTE",
|
"qte": "Kỹ năng QTE",
|
||||||
"mazenormal": "Mê cung (Thường)",
|
"mazenormal": "Mê cung (Thường)",
|
||||||
"level": "Cấp độ",
|
"level": "Cấp độ",
|
||||||
"relics": "Di vật",
|
"relics": "Di vật",
|
||||||
"eidolons": "Tinh hồn",
|
"eidolons": "Tinh hồn",
|
||||||
"lightcones": "Nón Ánh sáng",
|
"lightcones": "Nón Ánh sáng",
|
||||||
"trailblazer": "Nhà khai phá"
|
"trailblazer": "Nhà khai phá"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-111
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"TabTitle": {
|
"TabTitle": {
|
||||||
"title": "Firefly 分析器",
|
"title": "Firefly 分析器",
|
||||||
"description": "Veritas 分析工具"
|
"description": "Veritas 分析工具"
|
||||||
},
|
},
|
||||||
"DataAnalysisPage": {
|
"DataAnalysisPage": {
|
||||||
"useSkill": "使用技能",
|
"useSkill": "使用技能",
|
||||||
"totalDamage": "总伤害",
|
"totalDamage": "总伤害",
|
||||||
"damagePerAV": "每行动值伤害",
|
"damagePerAV": "每行动值伤害",
|
||||||
"totalAV": "总行动值",
|
"totalAV": "总行动值",
|
||||||
"skillType": "技能类型",
|
"skillType": "技能类型",
|
||||||
"skillName": "技能名称",
|
"skillName": "技能名称",
|
||||||
"actionValue": "行动值",
|
"actionValue": "行动值",
|
||||||
"character": "角色",
|
"character": "角色",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"path": "命途",
|
"path": "命途",
|
||||||
"rarity": "稀有度",
|
"rarity": "稀有度",
|
||||||
"damageByActionValue": "此行动值的伤害",
|
"damageByActionValue": "此行动值的伤害",
|
||||||
"element": "属性",
|
"element": "属性",
|
||||||
"totalTurn": "总回合",
|
"totalTurn": "总回合",
|
||||||
"technique": "密技",
|
"technique": "密技",
|
||||||
"talent": "天赋",
|
"talent": "天赋",
|
||||||
"basic": "普攻",
|
"basic": "普攻",
|
||||||
"skill": "战技",
|
"skill": "战技",
|
||||||
"ultimate": "终结技",
|
"ultimate": "终结技",
|
||||||
"servant": "忆灵",
|
"servant": "忆灵",
|
||||||
"skillDamageBreakdown": "技能类别",
|
"skillDamageBreakdown": "技能类别",
|
||||||
"skillUsageDistribution": "技能使用分布",
|
"skillUsageDistribution": "技能使用分布",
|
||||||
"damageOverTime": "累计伤害",
|
"damageOverTime": "累计伤害",
|
||||||
"damage": "伤害",
|
"damage": "伤害",
|
||||||
"cumulativeDamage": "累计伤害量",
|
"cumulativeDamage": "累计伤害量",
|
||||||
"characterInformation": "角色信息",
|
"characterInformation": "角色信息",
|
||||||
"turnDetail": "轮次详情",
|
"turnDetail": "轮次详情",
|
||||||
"damageDetails": "伤害详情",
|
"damageDetails": "伤害详情",
|
||||||
"cycleCount": "轮次",
|
"cycleCount": "轮次",
|
||||||
"chartInfo": "图表信息",
|
"chartInfo": "图表信息",
|
||||||
"actionBar": "行动轴",
|
"actionBar": "行动轴",
|
||||||
"lineupInfo": "阵容信息",
|
"lineupInfo": "阵容信息",
|
||||||
"loadData": "加载战斗数据",
|
"loadData": "加载战斗数据",
|
||||||
"exportData": "导出战斗数据",
|
"exportData": "导出战斗数据",
|
||||||
"connectSetting": "连接设置",
|
"connectSetting": "连接设置",
|
||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
"unconnected": "未连接",
|
"unconnected": "未连接",
|
||||||
"socketConnection": "socket连接",
|
"socketConnection": "socket连接",
|
||||||
"connectionType": "连接类型",
|
"connectionType": "连接类型",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"connect": "连接",
|
"connect": "连接",
|
||||||
"checkGameConnect": "检查游戏连接",
|
"checkGameConnect": "检查游戏连接",
|
||||||
"other": "其他",
|
"other": "其他",
|
||||||
"host": "主机",
|
"host": "主机",
|
||||||
"port": "端口",
|
"port": "端口",
|
||||||
"hostPlaceHolder": "输入主机地址",
|
"hostPlaceHolder": "输入主机地址",
|
||||||
"portPlaceHolder": "输入端口号",
|
"portPlaceHolder": "输入端口号",
|
||||||
"noDamageDetail": "没有可用的伤害详情",
|
"noDamageDetail": "没有可用的伤害详情",
|
||||||
"noCharactersInLineup": "队伍中没有角色",
|
"noCharactersInLineup": "队伍中没有角色",
|
||||||
"noTurns": "尚未有回合",
|
"noTurns": "尚未有回合",
|
||||||
"type": "类型 ",
|
"type": "类型 ",
|
||||||
"warrior": "毁灭",
|
"warrior": "毁灭",
|
||||||
"knight": "存护",
|
"knight": "存护",
|
||||||
"mage": "智识",
|
"mage": "智识",
|
||||||
"priest": "丰饶",
|
"priest": "丰饶",
|
||||||
"rogue": "巡猎",
|
"rogue": "巡猎",
|
||||||
"shaman": "同协",
|
"shaman": "同协",
|
||||||
"warlock": "虚无",
|
"warlock": "虚无",
|
||||||
"memory": "记忆",
|
"memory": "记忆",
|
||||||
"elation": "欢愉",
|
"elation": "欢愉",
|
||||||
"fire": "火",
|
"fire": "火",
|
||||||
"ice": "冰",
|
"ice": "冰",
|
||||||
"imaginary": "虚数",
|
"imaginary": "虚数",
|
||||||
"physical": "物理",
|
"physical": "物理",
|
||||||
"quantum": "量子",
|
"quantum": "量子",
|
||||||
"thunder": "雷",
|
"thunder": "雷",
|
||||||
"wind": "风",
|
"wind": "风",
|
||||||
"cycle": "回合",
|
"cycle": "回合",
|
||||||
"wave": "波次",
|
"wave": "波次",
|
||||||
"hp": "生命值",
|
"hp": "生命值",
|
||||||
"atk": "攻击力",
|
"atk": "攻击力",
|
||||||
"speed": "速度",
|
"speed": "速度",
|
||||||
"critRate": "暴击率",
|
"critRate": "暴击率",
|
||||||
"critDmg": "暴击伤害",
|
"critDmg": "暴击伤害",
|
||||||
"breakEffect": "击破特攻",
|
"breakEffect": "击破特攻",
|
||||||
"effectRes": "效果抵抗",
|
"effectRes": "效果抵抗",
|
||||||
"energyRegenerationRate": "能量回复效率",
|
"energyRegenerationRate": "能量回复效率",
|
||||||
"effectHitRate": "效果命中",
|
"effectHitRate": "效果命中",
|
||||||
"outgoingHealingBoost": "治疗量加成",
|
"outgoingHealingBoost": "治疗量加成",
|
||||||
"fireDmgBoost": "火属性伤害提高",
|
"fireDmgBoost": "火属性伤害提高",
|
||||||
"iceDmgBoost": "冰属性伤害提高",
|
"iceDmgBoost": "冰属性伤害提高",
|
||||||
"imaginaryDmgBoost": "虚数属性伤害提高",
|
"imaginaryDmgBoost": "虚数属性伤害提高",
|
||||||
"physicalDmgBoost": "物理属性伤害提高",
|
"physicalDmgBoost": "物理属性伤害提高",
|
||||||
"quantumDmgBoost": "量子属性伤害提高",
|
"quantumDmgBoost": "量子属性伤害提高",
|
||||||
"thunderDmgBoost": "雷属性伤害提高",
|
"thunderDmgBoost": "雷属性伤害提高",
|
||||||
"windDmgBoost": "风属性伤害提高",
|
"windDmgBoost": "风属性伤害提高",
|
||||||
"pursued": "附加伤害",
|
"pursued": "附加伤害",
|
||||||
"true damage": "真实伤害",
|
"true damage": "真实伤害",
|
||||||
"elation damage": "欢愉伤害",
|
"elation damage": "欢愉伤害",
|
||||||
"follow-up": "追加攻击",
|
"follow-up": "追加攻击",
|
||||||
"elemental damage": "击破与超击破伤害",
|
"elemental damage": "击破与超击破伤害",
|
||||||
"dot": "持续伤害 ",
|
"dot": "持续伤害 ",
|
||||||
"damagePerCycle": "每轮伤害",
|
"damagePerCycle": "每轮伤害",
|
||||||
"damagePerCycleAndWave": "每轮/波伤害",
|
"damagePerCycleAndWave": "每轮/波伤害",
|
||||||
"damagePerWave": "每波伤害",
|
"damagePerWave": "每波伤害",
|
||||||
"lastTurn": "最后一回合",
|
"lastTurn": "最后一回合",
|
||||||
"qte": "QTE技能",
|
"qte": "QTE技能",
|
||||||
"mazenormal": "普通迷宫",
|
"mazenormal": "普通迷宫",
|
||||||
"level": "等级",
|
"level": "等级",
|
||||||
"relics": "遗器",
|
"relics": "遗器",
|
||||||
"eidolons": "星魂",
|
"eidolons": "星魂",
|
||||||
"lightcones": "光锥",
|
"lightcones": "光锥",
|
||||||
"trailblazer": "开拓者"
|
"trailblazer": "开拓者"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { getDataCache } from "@/lib/cache/cache"
|
import { getDataCache } from "@/lib/cache/cache"
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: Request,
|
req: Request,
|
||||||
{ params }: { params: Promise<{ name: string }> }
|
{ params }: { params: Promise<{ name: string }> }
|
||||||
) {
|
) {
|
||||||
const { name } = await params
|
const { name } = await params
|
||||||
|
|
||||||
const item = getDataCache(name)
|
const item = getDataCache(name)
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return new Response("Not found", { status: 404 })
|
return new Response("Not found", { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Cache-Control": "public, max-age=3600"
|
"Cache-Control": "public, max-age=3600"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === "br") {
|
if (item.type === "br") {
|
||||||
headers["Content-Encoding"] = "br"
|
headers["Content-Encoding"] = "br"
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(new Uint8Array(item.buf), { headers })
|
return new Response(new Uint8Array(item.buf), { headers })
|
||||||
}
|
}
|
||||||
+282
-282
@@ -1,283 +1,283 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import useAvatarDataStore from "@/stores/avatarDataStore";
|
import useAvatarDataStore from "@/stores/avatarDataStore";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import useLocaleStore from "@/stores/localeStore";
|
import useLocaleStore from "@/stores/localeStore";
|
||||||
import { attackTypeToString, CharacterBasic } from "@/types";
|
import { attackTypeToString, CharacterBasic } from "@/types";
|
||||||
import { SkillBattleInfo } from "@/types/mics";
|
import { SkillBattleInfo } from "@/types/mics";
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { getNameChar } from "@/helper";
|
import { getNameChar } from "@/helper";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import NameAvatar from "../nameAvatar";
|
import NameAvatar from "../nameAvatar";
|
||||||
|
|
||||||
export default function ActionBar() {
|
export default function ActionBar() {
|
||||||
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
|
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
|
||||||
const [selectAvatar, setSelectAvatar] = useState<CharacterBasic | null>(null);
|
const [selectAvatar, setSelectAvatar] = useState<CharacterBasic | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore();
|
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore();
|
||||||
const { listAvatar } = useAvatarDataStore();
|
const { listAvatar } = useAvatarDataStore();
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const turnListRef = useRef<HTMLDivElement>(null);
|
const turnListRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
||||||
const contentStyle: React.CSSProperties = {
|
const contentStyle: React.CSSProperties = {
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShow = (modalId: string, avatar: CharacterBasic, turn: SkillBattleInfo) => {
|
const handleShow = (modalId: string, avatar: CharacterBasic, turn: SkillBattleInfo) => {
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
setSelectAvatar(avatar);
|
setSelectAvatar(avatar);
|
||||||
setSelectTurn(turn);
|
setSelectTurn(turn);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
modal.showModal();
|
modal.showModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close modal handler
|
// Close modal handler
|
||||||
const handleCloseModal = (modalId: string) => {
|
const handleCloseModal = (modalId: string) => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setSelectAvatar(null);
|
setSelectAvatar(null);
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.close();
|
modal.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle ESC key to close modal
|
// Handle ESC key to close modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscKey = (event: KeyboardEvent) => {
|
const handleEscKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape' && isModalOpen) {
|
if (event.key === 'Escape' && isModalOpen) {
|
||||||
handleCloseModal("action_detail_modal");
|
handleCloseModal("action_detail_modal");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleEscKey);
|
window.addEventListener('keydown', handleEscKey);
|
||||||
return () => window.removeEventListener('keydown', handleEscKey);
|
return () => window.removeEventListener('keydown', handleEscKey);
|
||||||
}, [isModalOpen]);
|
}, [isModalOpen]);
|
||||||
|
|
||||||
// Scroll to the bottom when new turns are added
|
// Scroll to the bottom when new turns are added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (turnListRef.current && skillHistory.length > 0) {
|
if (turnListRef.current && skillHistory.length > 0) {
|
||||||
turnListRef.current.scrollTop = turnListRef.current.scrollHeight;
|
turnListRef.current.scrollTop = turnListRef.current.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [skillHistory.length]);
|
}, [skillHistory.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
|
<div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
className="text-center text-xl lg:text-2xl mb-2 font-bold text-transparent bg-clip-text bg-linear-to-r from-pink-500 via-purple-500 to-cyan-500"
|
className="text-center text-xl lg:text-2xl mb-2 font-bold text-transparent bg-clip-text bg-linear-to-r from-pink-500 via-purple-500 to-cyan-500"
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
{transI18n("actionBar")}
|
{transI18n("actionBar")}
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-1 xl:grid-cols-2 items-center justify-items-center w-full gap-2 mb-2 mx-2">
|
<div className="grid grid-cols-2 md:grid-cols-1 xl:grid-cols-2 items-center justify-items-center w-full gap-2 mb-2 mx-2">
|
||||||
<div className="badge badge-soft badge-info gap-2 text-lg py-3">
|
<div className="badge badge-soft badge-info gap-2 text-lg py-3">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="size-6">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="size-6">
|
||||||
<path fillRule="evenodd" d="M3 2.25a.75.75 0 0 1 .75.75v.54l1.838-.46a9.75 9.75 0 0 1 6.725.738l.108.054A8.25 8.25 0 0 0 18 4.524l3.11-.732a.75.75 0 0 1 .917.81 47.784 47.784 0 0 0 .005 10.337.75.75 0 0 1-.574.812l-3.114.733a9.75 9.75 0 0 1-6.594-.77l-.108-.054a8.25 8.25 0 0 0-5.69-.625l-2.202.55V21a.75.75 0 0 1-1.5 0V3A.75.75 0 0 1 3 2.25Z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M3 2.25a.75.75 0 0 1 .75.75v.54l1.838-.46a9.75 9.75 0 0 1 6.725.738l.108.054A8.25 8.25 0 0 0 18 4.524l3.11-.732a.75.75 0 0 1 .917.81 47.784 47.784 0 0 0 .005 10.337.75.75 0 0 1-.574.812l-3.114.733a9.75 9.75 0 0 1-6.594-.77l-.108-.054a8.25 8.25 0 0 0-5.69-.625l-2.202.55V21a.75.75 0 0 1-1.5 0V3A.75.75 0 0 1 3 2.25Z" clipRule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{waveIndex}/{maxWave}</span>
|
<span>{waveIndex}/{maxWave}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="badge badge-soft badge-error gap-2 text-lg py-3 items-center">
|
<div className="badge badge-soft badge-error gap-2 text-lg py-3 items-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor" className="size-6">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor" className="size-6">
|
||||||
<path d="M0 32C0 14.3 14.3 0 32 0L64 0 320 0l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 11c0 42.4-16.9 83.1-46.9 113.1L237.3 256l67.9 67.9c30 30 46.9 70.7 46.9 113.1l0 11c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0L64 512l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l0-11c0-42.4 16.9-83.1 46.9-113.1L146.7 256 78.9 188.1C48.9 158.1 32 117.4 32 75l0-11C14.3 64 0 49.7 0 32zM96 64l0 11c0 25.5 10.1 49.9 28.1 67.9L192 210.7l67.9-67.9c18-18 28.1-42.4 28.1-67.9l0-11L96 64zm0 384l192 0 0-11c0-25.5-10.1-49.9-28.1-67.9L192 301.3l-67.9 67.9c-18 18-28.1 42.4-28.1 67.9l0 11z" />
|
<path d="M0 32C0 14.3 14.3 0 32 0L64 0 320 0l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 11c0 42.4-16.9 83.1-46.9 113.1L237.3 256l67.9 67.9c30 30 46.9 70.7 46.9 113.1l0 11c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0L64 512l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l0-11c0-42.4 16.9-83.1 46.9-113.1L146.7 256 78.9 188.1C48.9 158.1 32 117.4 32 75l0-11C14.3 64 0 49.7 0 32zM96 64l0 11c0 25.5 10.1 49.9 28.1 67.9L192 210.7l67.9-67.9c18-18 28.1-42.4 28.1-67.9l0-11L96 64zm0 384l192 0 0-11c0-25.5-10.1-49.9-28.1-67.9L192 301.3l-67.9 67.9c-18 18-28.1 42.4-28.1 67.9l0 11z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{cycleIndex}</span>
|
<span>{cycleIndex}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref={turnListRef}
|
ref={turnListRef}
|
||||||
className="flex md:block px-2 md:px-0 w-full pt-2 border-t-2 border-accent overflow-x-auto md:overflow-x-hidden md:overflow-y-auto max-h-[90vh] lg:max-h-[80vh]"
|
className="flex md:block px-2 md:px-0 w-full pt-2 border-t-2 border-accent overflow-x-auto md:overflow-x-hidden md:overflow-y-auto max-h-[90vh] lg:max-h-[80vh]"
|
||||||
>
|
>
|
||||||
<div className="flex flex-nowrap md:grid md:grid-cols-1 gap-2 w-fit md:w-full">
|
<div className="flex flex-nowrap md:grid md:grid-cols-1 gap-2 w-fit md:w-full">
|
||||||
{skillHistory.length === 0 ? (
|
{skillHistory.length === 0 ? (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<p className="text-base-content opacity-50">{transI18n("noTurns")}</p>
|
<p className="text-base-content opacity-50">{transI18n("noTurns")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
skillHistory.map((turn, index) => {
|
skillHistory.map((turn, index) => {
|
||||||
const data = listAvatar.find(it => it.id === turn.avatarId.toString());
|
const data = listAvatar.find(it => it.id === turn.avatarId.toString());
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
const text = getNameChar(locale, transI18n, data);
|
const text = getNameChar(locale, transI18n, data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className="h-full md:w-full">
|
<div key={index} className="h-full md:w-full">
|
||||||
<div
|
<div
|
||||||
onClick={() => handleShow("action_detail_modal", data, turn)}
|
onClick={() => handleShow("action_detail_modal", data, turn)}
|
||||||
className="h-full grid grid-cols-2 gap-2 border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer min-w-50 sm:min-w-62.5 md:min-w-0"
|
className="h-full grid grid-cols-2 gap-2 border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer min-w-50 sm:min-w-62.5 md:min-w-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={contentStyle}
|
style={contentStyle}
|
||||||
className="lg:col-span-1 grid grid-cols-1 items-center justify-center py-2"
|
className="lg:col-span-1 grid grid-cols-1 items-center justify-center py-2"
|
||||||
>
|
>
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
<div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4">
|
<div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4">
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
src={`${process.env.CDN_URL}/${data.icon}`}
|
src={`${process.env.CDN_URL}/${data.icon}`}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
alt={text}
|
alt={text}
|
||||||
width={125}
|
width={125}
|
||||||
height={125}
|
height={125}
|
||||||
className="w-8 h-8 object-contain"
|
className="w-8 h-8 object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
locale={locale}
|
locale={locale}
|
||||||
text={getNameChar(locale, transI18n, data)}
|
text={getNameChar(locale, transI18n, data)}
|
||||||
className="text-base-content text-center text-sm mt-1 font-medium"
|
className="text-base-content text-center text-sm mt-1 font-medium"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
|
<div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
|
||||||
<div className="bg-local text-primary text-xs max-w-full">
|
<div className="bg-local text-primary text-xs max-w-full">
|
||||||
{`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`}
|
{`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-primary text-xs max-w-full">
|
<div className="text-primary text-xs max-w-full">
|
||||||
{`${transI18n("totalDamage")}: ${Number(turn.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`}
|
{`${transI18n("totalDamage")}: ${Number(turn.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-primary text-xs max-w-full">
|
<div className="text-primary text-xs max-w-full">
|
||||||
{`${transI18n("actionValue")}: ${Number(turnHistory[turn.turnBattleId]?.actionValue || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`}
|
{`${transI18n("actionValue")}: ${Number(turnHistory[turn.turnBattleId]?.actionValue || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Character Detail Modal */}
|
{/* Character Detail Modal */}
|
||||||
<dialog id="action_detail_modal" className="modal sm:modal-middle backdrop-blur-sm">
|
<dialog id="action_detail_modal" className="modal sm:modal-middle backdrop-blur-sm">
|
||||||
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||||
<div className="sticky top-0 z-10">
|
<div className="sticky top-0 z-10">
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
||||||
onClick={() => handleCloseModal("action_detail_modal")}
|
onClick={() => handleCloseModal("action_detail_modal")}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
|
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
|
||||||
{transI18n("turnDetail").toUpperCase()}
|
{transI18n("turnDetail").toUpperCase()}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectAvatar && selectTurn && (
|
{selectAvatar && selectTurn && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="mt-4 w-full"
|
className="mt-4 w-full"
|
||||||
>
|
>
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
||||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 items-center">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 items-center">
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-base-content/70">{transI18n("id")}:</span>
|
<span className="font-medium text-base-content/70">{transI18n("id")}:</span>
|
||||||
<span className="font-bold">{selectAvatar.id}</span>
|
<span className="font-bold">{selectAvatar.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-base-content/70">{transI18n("character")}:</span>
|
<span className="font-medium text-base-content/70">{transI18n("character")}:</span>
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
locale={locale}
|
locale={locale}
|
||||||
text={getNameChar(locale, transI18n, selectAvatar)}
|
text={getNameChar(locale, transI18n, selectAvatar)}
|
||||||
className="font-bold"
|
className="font-bold"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
<Image
|
<Image
|
||||||
src={`${process.env.CDN_URL}/${selectAvatar?.icon}`}
|
src={`${process.env.CDN_URL}/${selectAvatar?.icon}`}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
alt={getNameChar(locale, transI18n, selectAvatar)}
|
alt={getNameChar(locale, transI18n, selectAvatar)}
|
||||||
width={80}
|
width={80}
|
||||||
height={80}
|
height={80}
|
||||||
className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20"
|
className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<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">
|
<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("skillType")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("skillType")}</h4>
|
||||||
<p className="mt-2">{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}</p>
|
<p className="mt-2">{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<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("skillName")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("skillName")}</h4>
|
||||||
<p className="mt-2">{selectTurn?.skillName}</p>
|
<p className="mt-2">{selectTurn?.skillName}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<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">
|
<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("cycle")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("cycle")}</h4>
|
||||||
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].cycleIndex}</p>
|
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].cycleIndex}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<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("wave")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("wave")}</h4>
|
||||||
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].waveIndex}</p>
|
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].waveIndex}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<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">
|
<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>
|
<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">{Number(turnHistory[selectTurn?.turnBattleId].actionValue).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
|
<p className="mt-2">{Number(turnHistory[selectTurn?.turnBattleId].actionValue).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<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>
|
<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">{Number(selectTurn?.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
|
<p className="mt-2 font-bold text-lg">{Number(selectTurn?.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
||||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("damageDetails")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("damageDetails")}</h4>
|
||||||
{selectTurn?.damageDetail?.length > 0 ? (
|
{selectTurn?.damageDetail?.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 mt-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 mt-3">
|
||||||
{selectTurn.damageDetail.map((detail, idx) => (
|
{selectTurn.damageDetail.map((detail, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="flex flex-col items-start gap-1 p-3 rounded-lg shadow bg-base-200"
|
className="flex flex-col items-start gap-1 p-3 rounded-lg shadow bg-base-200"
|
||||||
>
|
>
|
||||||
<span className="text-lg font-semibold text-primary">
|
<span className="text-lg font-semibold text-primary">
|
||||||
{Number(detail.damage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}
|
{Number(detail.damage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs uppercase text-gray-500 tracking-wide">
|
<span className="text-xs uppercase text-gray-500 tracking-wide">
|
||||||
{transI18n(attackTypeToString(detail?.damage_type).toLowerCase())}
|
{transI18n(attackTypeToString(detail?.damage_type).toLowerCase())}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="mt-2 italic opacity-70">{transI18n("noDamageDetail")}</p>
|
<p className="mt-2 italic opacity-70">{transI18n("noDamageDetail")}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</dialog>
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,99 +1,99 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { getNameChar } from '@/helper';
|
import { getNameChar } from '@/helper';
|
||||||
import useLocaleStore from '@/stores/localeStore';
|
import useLocaleStore from '@/stores/localeStore';
|
||||||
import { CharacterBasic } from '@/types';
|
import { CharacterBasic } from '@/types';
|
||||||
import NameAvatar from '../nameAvatar';
|
import NameAvatar from '../nameAvatar';
|
||||||
import useBattleDataStore from '@/stores/battleDataStore';
|
import useBattleDataStore from '@/stores/battleDataStore';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
interface CharacterCardProps {
|
interface CharacterCardProps {
|
||||||
data: CharacterBasic
|
data: CharacterBasic
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CharacterCard({ data }: CharacterCardProps) {
|
export default function CharacterCard({ data }: CharacterCardProps) {
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
const { avatarDetail } = useBattleDataStore()
|
const { avatarDetail } = useBattleDataStore()
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const text = getNameChar(locale, transI18n, data)
|
const text = getNameChar(locale, transI18n, data)
|
||||||
|
|
||||||
return (
|
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">
|
<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">
|
||||||
<div
|
<div
|
||||||
className={`w-20 rounded-md p-0.5 bg-linear-to-br ${data.rank === "CombatPowerAvatarRarityType5"
|
className={`w-20 rounded-md p-0.5 bg-linear-to-br ${data.rank === "CombatPowerAvatarRarityType5"
|
||||||
? "from-yellow-400 via-yellow-300 to-yellow-500"
|
? "from-yellow-400 via-yellow-300 to-yellow-500"
|
||||||
: "from-purple-300 via-purple-200 to-purple-400"
|
: "from-purple-300 via-purple-200 to-purple-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
<Image
|
<Image
|
||||||
width={376}
|
width={376}
|
||||||
height={512}
|
height={512}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`${process.env.CDN_URL}/${data.icon}`}
|
src={`${process.env.CDN_URL}/${data.icon}`}
|
||||||
className="w-full h-full rounded-md object-cover"
|
className="w-full h-full rounded-md object-cover"
|
||||||
alt="ALT"
|
alt="ALT"
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`/icon/${data?.damageType?.toLowerCase()}.webp`}
|
src={`/icon/${data?.damageType?.toLowerCase()}.webp`}
|
||||||
className="absolute top-0 left-0 w-6 h-6"
|
className="absolute top-0 left-0 w-6 h-6"
|
||||||
alt={data?.damageType?.toLowerCase()}
|
alt={data?.damageType?.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`/icon/${data.baseType.toLowerCase()}.webp`}
|
src={`/icon/${data.baseType.toLowerCase()}.webp`}
|
||||||
className="absolute top-0 right-0 w-6 h-6"
|
className="absolute top-0 right-0 w-6 h-6"
|
||||||
alt={data.baseType.toLowerCase()}
|
alt={data.baseType.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
locale={locale}
|
locale={locale}
|
||||||
text={text}
|
text={text}
|
||||||
className="mt-2 text-center text-base font-normal leading-tight"
|
className="mt-2 text-center text-base font-normal leading-tight"
|
||||||
/>
|
/>
|
||||||
{avatarDetail && (
|
{avatarDetail && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-xs text-base-content/70 mx-1">HP:</span>
|
<span className="text-xs text-base-content/70 mx-1">HP:</span>
|
||||||
<span className="text-xs font-medium">
|
<span className="text-xs font-medium">
|
||||||
<span className="text-error">
|
<span className="text-error">
|
||||||
{Number(avatarDetail?.[Number(data.id)]?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
{Number(avatarDetail?.[Number(data.id)]?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base-content/50">/</span>
|
<span className="text-base-content/50">/</span>
|
||||||
<span className="text-base-content/70">
|
<span className="text-base-content/70">
|
||||||
{Number(avatarDetail?.[Number(data.id)]?.stats?.MaxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
{Number(avatarDetail?.[Number(data.id)]?.stats?.MaxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-full bg-base-300 rounded-full h-2.5">
|
<div className="relative w-full bg-base-300 rounded-full h-2.5">
|
||||||
<div
|
<div
|
||||||
className="bg-error h-2.5 rounded-full transition-all duration-300"
|
className="bg-error h-2.5 rounded-full transition-all duration-300"
|
||||||
style={{
|
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, ((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">
|
<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)}%
|
{Math.round(((avatarDetail?.[Number(data.id)]?.stats?.HP || 0) / (avatarDetail?.[Number(data.id)]?.stats?.MaxHP || 100)) * 100)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,84 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
LineElement,
|
LineElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line } from 'react-chartjs-2';
|
||||||
import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll';
|
import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll';
|
||||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||||
import useLocaleStore from '@/stores/localeStore';
|
import useLocaleStore from '@/stores/localeStore';
|
||||||
import { getNameChar } from '@/helper';
|
import { getNameChar } from '@/helper';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend);
|
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend);
|
||||||
|
|
||||||
const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c'];
|
const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c'];
|
||||||
|
|
||||||
export default function MultiCharLineChart() {
|
export default function MultiCharLineChart() {
|
||||||
const [mode, setMode] = useState<1 | 2>(2);
|
const [mode, setMode] = useState<1 | 2>(2);
|
||||||
const dataByAvatar = useDamageLinesForAll(mode);
|
const dataByAvatar = useDamageLinesForAll(mode);
|
||||||
const avatarIds = Object.keys(dataByAvatar).map(Number);
|
const avatarIds = Object.keys(dataByAvatar).map(Number);
|
||||||
const { mapAvatar } = useAvatarDataStore()
|
const { mapAvatar } = useAvatarDataStore()
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
const transI18n = useTranslations("DataAnalysisPage")
|
const transI18n = useTranslations("DataAnalysisPage")
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
datasets: avatarIds.map((id, idx) => ({
|
datasets: avatarIds.map((id, idx) => ({
|
||||||
label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]),
|
label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]),
|
||||||
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => {
|
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => {
|
||||||
return {
|
return {
|
||||||
x: x.toFixed(2),
|
x: x.toFixed(2),
|
||||||
y: y.toFixed(2)
|
y: y.toFixed(2)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
borderColor: colors[idx % colors.length],
|
borderColor: colors[idx % colors.length],
|
||||||
backgroundColor: colors[idx % colors.length],
|
backgroundColor: colors[idx % colors.length],
|
||||||
fill: false,
|
fill: false,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
type: 'linear' as const,
|
type: 'linear' as const,
|
||||||
position: 'bottom' as "bottom" | "center" | "left" | "top" | "right",
|
position: 'bottom' as "bottom" | "center" | "left" | "top" | "right",
|
||||||
ticks: {
|
ticks: {
|
||||||
stepSize: 1,
|
stepSize: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 1 | 2)}
|
onClick={() => setMode(m as 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Line data={data} options={options} />
|
<Line data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,80 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
import { ChartOptions } from "chart.js";
|
import { ChartOptions } from "chart.js";
|
||||||
import { useDamageLineForOne } from "@/hooks/useDamageLineForOne";
|
import { useDamageLineForOne } from "@/hooks/useDamageLineForOne";
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
Filler,
|
Filler,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
Filler
|
Filler
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export function DamageLineForOne({ avatarId }: { avatarId: number; }) {
|
export function DamageLineForOne({ avatarId }: { avatarId: number; }) {
|
||||||
const [mode, setMode] = useState<0 | 1>(1);
|
const [mode, setMode] = useState<0 | 1>(1);
|
||||||
const dataRaw = useDamageLineForOne(avatarId, mode);
|
const dataRaw = useDamageLineForOne(avatarId, mode);
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const data = {
|
const data = {
|
||||||
labels: dataRaw.map(d => Number(d.x.toFixed(2))),
|
labels: dataRaw.map(d => Number(d.x.toFixed(2))),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: mode === 1 ? transI18n("cumulativeDamage") : transI18n("damageByActionValue"),
|
label: mode === 1 ? transI18n("cumulativeDamage") : transI18n("damageByActionValue"),
|
||||||
data: dataRaw.map(d => Number(d.y.toFixed(2))),
|
data: dataRaw.map(d => Number(d.y.toFixed(2))),
|
||||||
borderColor: "rgba(75,192,192,1)",
|
borderColor: "rgba(75,192,192,1)",
|
||||||
backgroundColor: "rgba(75,192,192,0.2)",
|
backgroundColor: "rgba(75,192,192,0.2)",
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: ChartOptions<"line"> = {
|
const options: ChartOptions<"line"> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: true },
|
legend: { display: true },
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: { title: { display: true, text: transI18n("actionValue") } },
|
x: { title: { display: true, text: transI18n("actionValue") } },
|
||||||
y: { title: { display: true, text: transI18n("damage") }, beginAtZero: true },
|
y: { title: { display: true, text: transI18n("damage") }, beginAtZero: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 0 | 1)}
|
onClick={() => setMode(m as 0 | 1)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Line data={data} options={options} />
|
<Line data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,177 +1,177 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { getNameChar } from '@/helper';
|
import { getNameChar } from '@/helper';
|
||||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||||
import useBattleDataStore from '@/stores/battleDataStore';
|
import useBattleDataStore from '@/stores/battleDataStore';
|
||||||
import useLocaleStore from '@/stores/localeStore';
|
import useLocaleStore from '@/stores/localeStore';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
BarElement,
|
BarElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend
|
Legend
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Bar } from 'react-chartjs-2';
|
import { Bar } from 'react-chartjs-2';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import {attackTypeToString} from "@/types";
|
import {attackTypeToString} from "@/types";
|
||||||
|
|
||||||
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
||||||
|
|
||||||
const colorPalette = [
|
const colorPalette = [
|
||||||
'rgba(255, 99, 132, 0.6)',
|
'rgba(255, 99, 132, 0.6)',
|
||||||
'rgba(54, 162, 235, 0.6)',
|
'rgba(54, 162, 235, 0.6)',
|
||||||
'rgba(255, 206, 86, 0.6)',
|
'rgba(255, 206, 86, 0.6)',
|
||||||
'rgba(75, 192, 192, 0.6)',
|
'rgba(75, 192, 192, 0.6)',
|
||||||
'rgba(153, 102, 255, 0.6)',
|
'rgba(153, 102, 255, 0.6)',
|
||||||
'rgba(255, 159, 64, 0.6)',
|
'rgba(255, 159, 64, 0.6)',
|
||||||
'rgba(199, 199, 199, 0.6)',
|
'rgba(199, 199, 199, 0.6)',
|
||||||
'rgba(255, 99, 255, 0.6)',
|
'rgba(255, 99, 255, 0.6)',
|
||||||
'rgba(0, 200, 83, 0.6)',
|
'rgba(0, 200, 83, 0.6)',
|
||||||
'rgba(255, 112, 67, 0.6)',
|
'rgba(255, 112, 67, 0.6)',
|
||||||
'rgba(63, 81, 181, 0.6)',
|
'rgba(63, 81, 181, 0.6)',
|
||||||
'rgba(0, 188, 212, 0.6)',
|
'rgba(0, 188, 212, 0.6)',
|
||||||
'rgba(233, 30, 99, 0.6)',
|
'rgba(233, 30, 99, 0.6)',
|
||||||
'rgba(124, 179, 66, 0.6)',
|
'rgba(124, 179, 66, 0.6)',
|
||||||
'rgba(255, 235, 59, 0.6)',
|
'rgba(255, 235, 59, 0.6)',
|
||||||
'rgba(158, 158, 158, 0.6)',
|
'rgba(158, 158, 158, 0.6)',
|
||||||
'rgba(121, 85, 72, 0.6)',
|
'rgba(121, 85, 72, 0.6)',
|
||||||
'rgba(96, 125, 139, 0.6)',
|
'rgba(96, 125, 139, 0.6)',
|
||||||
'rgba(100, 181, 246, 0.6)',
|
'rgba(100, 181, 246, 0.6)',
|
||||||
'rgba(255, 171, 145, 0.6)',
|
'rgba(255, 171, 145, 0.6)',
|
||||||
'rgba(174, 213, 129, 0.6)',
|
'rgba(174, 213, 129, 0.6)',
|
||||||
'rgba(255, 245, 157, 0.6)'
|
'rgba(255, 245, 157, 0.6)'
|
||||||
];
|
];
|
||||||
|
|
||||||
const borderPalette = colorPalette.map((c) => c.replace('0.6', '1'));
|
const borderPalette = colorPalette.map((c) => c.replace('0.6', '1'));
|
||||||
|
|
||||||
export default function DamagePerAvatarForAll() {
|
export default function DamagePerAvatarForAll() {
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const { lineup, skillHistory } = useBattleDataStore();
|
const { lineup, skillHistory } = useBattleDataStore();
|
||||||
const { mapAvatar } = useAvatarDataStore();
|
const { mapAvatar } = useAvatarDataStore();
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
|
|
||||||
const [mode, setMode] = useState<number>(2);
|
const [mode, setMode] = useState<number>(2);
|
||||||
|
|
||||||
const avatarMap = lineup.map((avatar) => {
|
const avatarMap = lineup.map((avatar) => {
|
||||||
if (!mapAvatar?.[avatar.avatarId.toString()]) return undefined;
|
if (!mapAvatar?.[avatar.avatarId.toString()]) return undefined;
|
||||||
return {
|
return {
|
||||||
avatarId: avatar.avatarId,
|
avatarId: avatar.avatarId,
|
||||||
avatarName: getNameChar(locale, transI18n, mapAvatar?.[avatar.avatarId.toString()])
|
avatarName: getNameChar(locale, transI18n, mapAvatar?.[avatar.avatarId.toString()])
|
||||||
};
|
};
|
||||||
}).filter(Boolean) as { avatarId: number, avatarName: string }[];
|
}).filter(Boolean) as { avatarId: number, avatarName: string }[];
|
||||||
|
|
||||||
const labels = avatarMap.map(a => a.avatarName);
|
const labels = avatarMap.map(a => a.avatarName);
|
||||||
|
|
||||||
let datasets: Array<{
|
let datasets: Array<{
|
||||||
label: string;
|
label: string;
|
||||||
data: number[];
|
data: number[];
|
||||||
backgroundColor: string | string[];
|
backgroundColor: string | string[];
|
||||||
borderColor: string | string[];
|
borderColor: string | string[];
|
||||||
borderWidth: number;
|
borderWidth: number;
|
||||||
stack?: string;
|
stack?: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
if (mode === 1) {
|
if (mode === 1) {
|
||||||
datasets = [
|
datasets = [
|
||||||
{
|
{
|
||||||
label: transI18n("totalDamage"),
|
label: transI18n("totalDamage"),
|
||||||
data: avatarMap.map(({ avatarId }) => {
|
data: avatarMap.map(({ avatarId }) => {
|
||||||
const total = skillHistory
|
const total = skillHistory
|
||||||
.filter(skill => skill.avatarId === avatarId)
|
.filter(skill => skill.avatarId === avatarId)
|
||||||
.reduce((sum, s) => sum + s.totalDamage, 0);
|
.reduce((sum, s) => sum + s.totalDamage, 0);
|
||||||
return total;
|
return total;
|
||||||
}),
|
}),
|
||||||
backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]),
|
backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]),
|
||||||
borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]),
|
borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]),
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 2) {
|
if (mode === 2) {
|
||||||
const damageTypesSet = new Set<string>();
|
const damageTypesSet = new Set<string>();
|
||||||
avatarMap.forEach(({ avatarId }) => {
|
avatarMap.forEach(({ avatarId }) => {
|
||||||
skillHistory.filter(s => s.avatarId === avatarId).forEach(s =>
|
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 => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase())))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const damageTypes = Array.from(damageTypesSet);
|
const damageTypes = Array.from(damageTypesSet);
|
||||||
|
|
||||||
datasets = damageTypes.map((dt, i) => ({
|
datasets = damageTypes.map((dt, i) => ({
|
||||||
label: dt,
|
label: dt,
|
||||||
data: avatarMap.map(({ avatarId }) => {
|
data: avatarMap.map(({ avatarId }) => {
|
||||||
const total = skillHistory
|
const total = skillHistory
|
||||||
.filter(skill => skill.avatarId === avatarId)
|
.filter(skill => skill.avatarId === avatarId)
|
||||||
.flatMap(s => s.damageDetail || [])
|
.flatMap(s => s.damageDetail || [])
|
||||||
.filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt)
|
.filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt)
|
||||||
.reduce((sum, d) => sum + d.damage, 0);
|
.reduce((sum, d) => sum + d.damage, 0);
|
||||||
return total;
|
return total;
|
||||||
}),
|
}),
|
||||||
backgroundColor: colorPalette[i % colorPalette.length],
|
backgroundColor: colorPalette[i % colorPalette.length],
|
||||||
borderColor: borderPalette[i % borderPalette.length],
|
borderColor: borderPalette[i % borderPalette.length],
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
stack: transI18n('damage')
|
stack: transI18n('damage')
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 3) {
|
if (mode === 3) {
|
||||||
const skillTypesSet = new Set<string>();
|
const skillTypesSet = new Set<string>();
|
||||||
skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase())));
|
skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase())));
|
||||||
const skillTypes = Array.from(skillTypesSet);
|
const skillTypes = Array.from(skillTypesSet);
|
||||||
|
|
||||||
datasets = skillTypes.map((st, i) => ({
|
datasets = skillTypes.map((st, i) => ({
|
||||||
label: st,
|
label: st,
|
||||||
data: avatarMap.map(({ avatarId }) => {
|
data: avatarMap.map(({ avatarId }) => {
|
||||||
const total = skillHistory
|
const total = skillHistory
|
||||||
.filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st)
|
.filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st)
|
||||||
.reduce((sum, s) => sum + s.totalDamage, 0);
|
.reduce((sum, s) => sum + s.totalDamage, 0);
|
||||||
return total;
|
return total;
|
||||||
}),
|
}),
|
||||||
backgroundColor: colorPalette[i % colorPalette.length],
|
backgroundColor: colorPalette[i % colorPalette.length],
|
||||||
borderColor: borderPalette[i % borderPalette.length],
|
borderColor: borderPalette[i % borderPalette.length],
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
stack: transI18n('skill')
|
stack: transI18n('skill')
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
stacked: mode !== 1,
|
stacked: mode !== 1,
|
||||||
ticks: {
|
ticks: {
|
||||||
precision: 0,
|
precision: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
stacked: mode !== 1,
|
stacked: mode !== 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
{ mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
{ mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m)}
|
onClick={() => setMode(m)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Bar data={{ labels, datasets }} options={options}/>
|
<Bar data={{ labels, datasets }} options={options}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,60 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Bar } from "react-chartjs-2";
|
import { Bar } from "react-chartjs-2";
|
||||||
import { ChartOptions } from "chart.js";
|
import { ChartOptions } from "chart.js";
|
||||||
import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle";
|
import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle";
|
||||||
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
||||||
export default function DamagePerCycleForAll() {
|
export default function DamagePerCycleForAll() {
|
||||||
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
||||||
const dataRaw = useDamagePerCycleForAll(mode);
|
const dataRaw = useDamagePerCycleForAll(mode);
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const data = {
|
const data = {
|
||||||
labels: dataRaw.map(d => d.x),
|
labels: dataRaw.map(d => d.x),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
||||||
data: dataRaw.map(d => d.y),
|
data: dataRaw.map(d => d.y),
|
||||||
backgroundColor: dataRaw.map((_, i) =>
|
backgroundColor: dataRaw.map((_, i) =>
|
||||||
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: ChartOptions<"bar"> = {
|
const options: ChartOptions<"bar"> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: true },
|
legend: { display: true },
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: { title: { display: true, text: transI18n("cycleCount") } },
|
x: { title: { display: true, text: transI18n("cycleCount") } },
|
||||||
y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true },
|
y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 0 | 1 | 2)}
|
onClick={() => setMode(m as 0 | 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Bar data={data} options={options} />
|
<Bar data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Bar } from "react-chartjs-2";
|
import { Bar } from "react-chartjs-2";
|
||||||
import { ChartOptions } from "chart.js";
|
import { ChartOptions } from "chart.js";
|
||||||
import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle";
|
import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle";
|
||||||
|
|
||||||
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
||||||
export function DamagePerCycleForOne({
|
export function DamagePerCycleForOne({
|
||||||
avatarId,
|
avatarId,
|
||||||
}: {
|
}: {
|
||||||
avatarId: number;
|
avatarId: number;
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
||||||
const dataRaw = useDamagePerCycleForOne(avatarId, mode);
|
const dataRaw = useDamagePerCycleForOne(avatarId, mode);
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const data = {
|
const data = {
|
||||||
labels: dataRaw.map(d => d.x),
|
labels: dataRaw.map(d => d.x),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
||||||
data: dataRaw.map(d => d.y),
|
data: dataRaw.map(d => d.y),
|
||||||
backgroundColor: dataRaw.map((_, i) =>
|
backgroundColor: dataRaw.map((_, i) =>
|
||||||
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: ChartOptions<"bar"> = {
|
const options: ChartOptions<"bar"> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: true },
|
legend: { display: true },
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: { title: { display: true, text: transI18n("cycleCount") } },
|
x: { title: { display: true, text: transI18n("cycleCount") } },
|
||||||
y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true },
|
y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 0 | 1 | 2)}
|
onClick={() => setMode(m as 0 | 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Bar data={data} options={options} />
|
<Bar data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +1,91 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
ArcElement,
|
ArcElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend
|
Legend
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Pie } from 'react-chartjs-2';
|
import { Pie } from 'react-chartjs-2';
|
||||||
import { useDamagePercentPerAvatar } from '@/hooks/useDamagePercentPerAvatar';
|
import { useDamagePercentPerAvatar } from '@/hooks/useDamagePercentPerAvatar';
|
||||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||||
import useLocaleStore from '@/stores/localeStore';
|
import useLocaleStore from '@/stores/localeStore';
|
||||||
import { getNameChar } from '@/helper';
|
import { getNameChar } from '@/helper';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType';
|
import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType';
|
||||||
|
|
||||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
'#f87171', '#34d399', '#60a5fa', '#facc15',
|
'#f87171', '#34d399', '#60a5fa', '#facc15',
|
||||||
'#a78bfa', '#fb923c', '#f472b6', '#10b981',
|
'#a78bfa', '#fb923c', '#f472b6', '#10b981',
|
||||||
'#fcd34d', '#c084fc', '#f97316', '#ec4899',
|
'#fcd34d', '#c084fc', '#f97316', '#ec4899',
|
||||||
'#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e'
|
'#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e'
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function DamagePercentChartForAll() {
|
export default function DamagePercentChartForAll() {
|
||||||
const [mode, setMode] = useState<1 | 2>(1);
|
const [mode, setMode] = useState<1 | 2>(1);
|
||||||
const damageByAvatar = useDamagePercentPerAvatar();
|
const damageByAvatar = useDamagePercentPerAvatar();
|
||||||
const damageByType = useDamagePercentByType();
|
const damageByType = useDamagePercentByType();
|
||||||
const { mapAvatar } = useAvatarDataStore();
|
const { mapAvatar } = useAvatarDataStore();
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
|
|
||||||
const chartData = {
|
const chartData = {
|
||||||
labels: (mode === 1
|
labels: (mode === 1
|
||||||
? damageByAvatar.map(d =>
|
? damageByAvatar.map(d =>
|
||||||
getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()])
|
getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()])
|
||||||
)
|
)
|
||||||
: damageByType.map(d => transI18n(d.type.toLowerCase()))
|
: damageByType.map(d => transI18n(d.type.toLowerCase()))
|
||||||
),
|
),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: '% ' + transI18n("damage"),
|
label: '% ' + transI18n("damage"),
|
||||||
data: (mode === 1
|
data: (mode === 1
|
||||||
? damageByAvatar.map(d => d.percent.toFixed(2))
|
? damageByAvatar.map(d => d.percent.toFixed(2))
|
||||||
: damageByType.map(d => d.percent.toFixed(2))
|
: damageByType.map(d => d.percent.toFixed(2))
|
||||||
),
|
),
|
||||||
backgroundColor: colors,
|
backgroundColor: colors,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: (ctx: import('chart.js').TooltipItem<'pie'>) => `${ctx.label}: ${ctx.raw}%`,
|
label: (ctx: import('chart.js').TooltipItem<'pie'>) => `${ctx.label}: ${ctx.raw}%`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
datalabels: {
|
datalabels: {
|
||||||
display: false,
|
display: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 1 | 2)}
|
onClick={() => setMode(m as 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<Pie data={chartData} options={options} />
|
<Pie data={chartData} options={options} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +1,70 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Bar } from 'react-chartjs-2';
|
import { Bar } from 'react-chartjs-2';
|
||||||
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
|
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
||||||
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
||||||
|
|
||||||
|
|
||||||
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
||||||
|
|
||||||
export default function SkillBarChart({ avatarId }: { avatarId: number }) {
|
export default function SkillBarChart({ avatarId }: { avatarId: number }) {
|
||||||
const [mode, setMode] = useState<1 | 2>(2);
|
const [mode, setMode] = useState<1 | 2>(2);
|
||||||
|
|
||||||
const transI18n = useTranslations('DataAnalysisPage');
|
const transI18n = useTranslations('DataAnalysisPage');
|
||||||
const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId);
|
const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId);
|
||||||
const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId);
|
const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId);
|
||||||
|
|
||||||
const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c'];
|
const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c'];
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)),
|
labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: mode === 1
|
label: mode === 1
|
||||||
? transI18n('skill')
|
? transI18n('skill')
|
||||||
: transI18n('damage'),
|
: transI18n('damage'),
|
||||||
data: mode === 1 ? skillDamageValues : typeDamageValues,
|
data: mode === 1 ? skillDamageValues : typeDamageValues,
|
||||||
backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length),
|
backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length),
|
||||||
borderColor: '#111',
|
borderColor: '#111',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: { precision: 0 },
|
ticks: { precision: 0 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: false,
|
display: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 1 | 2)}
|
onClick={() => setMode(m as 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Bar data={data} options={options} />
|
<Bar data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,75 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Pie } from 'react-chartjs-2';
|
import { Pie } from 'react-chartjs-2';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
ArcElement,
|
ArcElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
||||||
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
||||||
|
|
||||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
'#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c',
|
'#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c',
|
||||||
'#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80',
|
'#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80',
|
||||||
'#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac',
|
'#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function SkillPieChart({ avatarId }: { avatarId: number }) {
|
export default function SkillPieChart({ avatarId }: { avatarId: number }) {
|
||||||
const [mode, setMode] = useState<1 | 2>(2);
|
const [mode, setMode] = useState<1 | 2>(2);
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
|
|
||||||
const skillData = useSkillDamageForAvatar(avatarId);
|
const skillData = useSkillDamageForAvatar(avatarId);
|
||||||
const typeData = useDamageByTypeForAvatar(avatarId);
|
const typeData = useDamageByTypeForAvatar(avatarId);
|
||||||
|
|
||||||
const { labels, damageValues } = mode === 1 ? skillData : typeData;
|
const { labels, damageValues } = mode === 1 ? skillData : typeData;
|
||||||
const total = damageValues.reduce((sum, val) => sum + val, 0);
|
const total = damageValues.reduce((sum, val) => sum + val, 0);
|
||||||
|
|
||||||
const labelsWithPercent = labels.map((label, index) => {
|
const labelsWithPercent = labels.map((label, index) => {
|
||||||
const value = damageValues[index];
|
const value = damageValues[index];
|
||||||
const percent = total ? ((value / total) * 100).toFixed(1) : '0.0';
|
const percent = total ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||||
return `${transI18n(label.toLowerCase())} (${percent}%)`;
|
return `${transI18n(label.toLowerCase())} (${percent}%)`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels: labelsWithPercent,
|
labels: labelsWithPercent,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
data: damageValues,
|
data: damageValues,
|
||||||
backgroundColor: COLORS.slice(0, labels.length),
|
backgroundColor: COLORS.slice(0, labels.length),
|
||||||
borderColor: '#fff',
|
borderColor: '#fff',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full ">
|
<div className="w-full ">
|
||||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||||
{[
|
{[
|
||||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||||
].map(({ mode: m, label, className }) => (
|
].map(({ mode: m, label, className }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
key={m}
|
||||||
onClick={() => setMode(m as 1 | 2)}
|
onClick={() => setMode(m as 1 | 2)}
|
||||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Pie
|
<Pie
|
||||||
data={data}
|
data={data}
|
||||||
options={{
|
options={{
|
||||||
responsive: true,
|
responsive: true,
|
||||||
cutout: '30%',
|
cutout: '30%',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,79 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import useAvatarDataStore from "@/stores/avatarDataStore";
|
import useAvatarDataStore from "@/stores/avatarDataStore";
|
||||||
import { getNameEnemy } from "@/helper/getNameChar";
|
import { getNameEnemy } from "@/helper/getNameChar";
|
||||||
|
|
||||||
import useLocaleStore from "@/stores/localeStore";
|
import useLocaleStore from "@/stores/localeStore";
|
||||||
import NameAvatar from "../nameAvatar";
|
import NameAvatar from "../nameAvatar";
|
||||||
|
|
||||||
export default function EnemyBar() {
|
export default function EnemyBar() {
|
||||||
const { enemyDetail } = useBattleDataStore()
|
const { enemyDetail } = useBattleDataStore()
|
||||||
const { mapEnemy } = useAvatarDataStore()
|
const { mapEnemy } = useAvatarDataStore()
|
||||||
const { locale } = useLocaleStore()
|
const { locale } = useLocaleStore()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-3 w-full">
|
<div className="p-3 w-full">
|
||||||
<div className="flex gap-3 overflow-x-auto pb-2">
|
<div className="flex gap-3 overflow-x-auto pb-2">
|
||||||
{enemyDetail && Object.values(enemyDetail).filter((enemy) => (
|
{enemyDetail && Object.values(enemyDetail).filter((enemy) => (
|
||||||
enemy.stats.HP <= enemy.maxHP)).map((enemy, uid) => (
|
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 shrink-0">
|
<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 justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{mapEnemy?.[enemy.id.toString()]?.icon && (
|
{mapEnemy?.[enemy.id.toString()]?.icon && (
|
||||||
<Image
|
<Image
|
||||||
src={`${process.env.CDN_URL}/${mapEnemy?.[enemy.id.toString()]?.icon}`}
|
src={`${process.env.CDN_URL}/${mapEnemy?.[enemy.id.toString()]?.icon}`}
|
||||||
alt={enemy.name}
|
alt={enemy.name}
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
className="object-cover w-10 h-10 rounded-lg"
|
className="object-cover w-10 h-10 rounded-lg"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
text={getNameEnemy(locale, mapEnemy?.[enemy.id.toString()])}
|
text={getNameEnemy(locale, mapEnemy?.[enemy.id.toString()])}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
className="text-base font-semibold leading-tight truncate overflow-hidden"
|
className="text-base font-semibold leading-tight truncate overflow-hidden"
|
||||||
/>
|
/>
|
||||||
<p className="text-base-content/70 text-xs">Level {enemy.level || 1}</p>
|
<p className="text-base-content/70 text-xs">Level {enemy.level || 1}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-xs text-base-content/70">HP:</div>
|
<div className="text-xs text-base-content/70">HP:</div>
|
||||||
<div className="flex text-xs font-medium">
|
<div className="flex text-xs font-medium">
|
||||||
<div className="text-error">
|
<div className="text-error">
|
||||||
{Number(enemy?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
{Number(enemy?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-base-content/50 mx-1">/</div>
|
<div className="text-base-content/50 mx-1">/</div>
|
||||||
<div className="text-base-content/70">
|
<div className="text-base-content/70">
|
||||||
{Number(enemy?.maxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
{Number(enemy?.maxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-full bg-base-300 rounded-full h-2.5">
|
<div className="relative w-full bg-base-300 rounded-full h-2.5">
|
||||||
<div
|
<div
|
||||||
className="bg-error h-2.5 rounded-full transition-all duration-300"
|
className="bg-error h-2.5 rounded-full transition-all duration-300"
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.max(0, Math.min(100, ((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100))}%`
|
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">
|
<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)}%
|
{Math.round(((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return(
|
return(
|
||||||
<footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10">
|
<footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10">
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<p>Copyright © {new Date().getFullYear()} - Kain (Powered by Nextjs & DaisyUi)</p>
|
<p>Copyright © {new Date().getFullYear()} - Kain (Powered by Nextjs & DaisyUi)</p>
|
||||||
</aside>
|
</aside>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+498
-498
@@ -1,499 +1,499 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { exportBattleData, importBattleData } from "@/helper";
|
import { exportBattleData, importBattleData } from "@/helper";
|
||||||
import { useChangeTheme } from "@/hooks/useChangeTheme";
|
import { useChangeTheme } from "@/hooks/useChangeTheme";
|
||||||
import { checkConnectTcpApi } from "@/lib/api";
|
import { checkConnectTcpApi } from "@/lib/api";
|
||||||
import { listCurrentLanguage } from "@/lib/constant";
|
import { listCurrentLanguage } from "@/lib/constant";
|
||||||
import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket";
|
import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import useLocaleStore from "@/stores/localeStore";
|
import useLocaleStore from "@/stores/localeStore";
|
||||||
import useSocketStore from "@/stores/socketSettingStore";
|
import useSocketStore from "@/stores/socketSettingStore";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const themes = [
|
const themes = [
|
||||||
{ label: "Winter" },
|
{ label: "Winter" },
|
||||||
{ label: "Night" },
|
{ label: "Night" },
|
||||||
{ label: "Cupcake" },
|
{ label: "Cupcake" },
|
||||||
{ label: "Coffee" },
|
{ label: "Coffee" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { changeTheme } = useChangeTheme()
|
const { changeTheme } = useChangeTheme()
|
||||||
const { locale, setLocale } = useLocaleStore()
|
const { locale, setLocale } = useLocaleStore()
|
||||||
const { loadBattleDataFromJSON, version } = useBattleDataStore()
|
const { loadBattleDataFromJSON, version } = useBattleDataStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const transI18n = useTranslations("DataAnalysisPage")
|
const transI18n = useTranslations("DataAnalysisPage")
|
||||||
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore();
|
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore();
|
||||||
const [message, setMessage] = useState({ text: '', type: '' });
|
const [message, setMessage] = useState({ text: '', type: '' });
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
const cookieLocale = document.cookie.split("; ")
|
const cookieLocale = document.cookie.split("; ")
|
||||||
.find((row) => row.startsWith("MYNEXTAPP_LOCALE"))
|
.find((row) => row.startsWith("MYNEXTAPP_LOCALE"))
|
||||||
?.split("=")[1];
|
?.split("=")[1];
|
||||||
|
|
||||||
if (cookieLocale) {
|
if (cookieLocale) {
|
||||||
if (!listCurrentLanguage.hasOwnProperty(cookieLocale)) {
|
if (!listCurrentLanguage.hasOwnProperty(cookieLocale)) {
|
||||||
setLocale("en")
|
setLocale("en")
|
||||||
} else {
|
} else {
|
||||||
setLocale(cookieLocale)
|
setLocale(cookieLocale)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
let browserLocale = navigator.language.slice(0, 2);
|
let browserLocale = navigator.language.slice(0, 2);
|
||||||
|
|
||||||
if (!listCurrentLanguage.hasOwnProperty(browserLocale)) {
|
if (!listCurrentLanguage.hasOwnProperty(browserLocale)) {
|
||||||
browserLocale = "en"
|
browserLocale = "en"
|
||||||
}
|
}
|
||||||
setLocale(browserLocale);
|
setLocale(browserLocale);
|
||||||
document.cookie = `MYNEXTAPP_LOCALE=${browserLocale};`
|
document.cookie = `MYNEXTAPP_LOCALE=${browserLocale};`
|
||||||
router.refresh()
|
router.refresh()
|
||||||
}
|
}
|
||||||
}, [router, setLocale])
|
}, [router, setLocale])
|
||||||
|
|
||||||
const changeLocale = (newLocale: string) => {
|
const changeLocale = (newLocale: string) => {
|
||||||
setLocale(newLocale)
|
setLocale(newLocale)
|
||||||
document.cookie = `MYNEXTAPP_LOCALE=${newLocale};`
|
document.cookie = `MYNEXTAPP_LOCALE=${newLocale};`
|
||||||
router.refresh()
|
router.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkStatus = () => {
|
const checkStatus = () => {
|
||||||
const connected = isSocketConnected();
|
const connected = isSocketConnected();
|
||||||
if (connected !== status) {
|
if (connected !== status) {
|
||||||
setStatus(connected);
|
setStatus(connected);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkStatus();
|
checkStatus();
|
||||||
const intervalId = setInterval(checkStatus, 5000);
|
const intervalId = setInterval(checkStatus, 5000);
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [status, setStatus]);
|
}, [status, setStatus]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const socket = getSocket();
|
const socket = getSocket();
|
||||||
|
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disconnectSocket()
|
disconnectSocket()
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleConnect = () => {
|
const handleConnect = () => {
|
||||||
if (!host || !port) {
|
if (!host || !port) {
|
||||||
setMessage({ text: 'IP and Port are required', type: 'error' });
|
setMessage({ text: 'IP and Port are required', type: 'error' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
connectSocket();
|
connectSocket();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const connected = isSocketConnected();
|
const connected = isSocketConnected();
|
||||||
setStatus(connected);
|
setStatus(connected);
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
setMessage({ text: 'Socket connected successfully!', type: 'success' });
|
setMessage({ text: 'Socket connected successfully!', type: 'success' });
|
||||||
} else {
|
} else {
|
||||||
setMessage({ text: 'Failed to connect. Please check IP and port.', type: 'error' });
|
setMessage({ text: 'Failed to connect. Please check IP and port.', type: 'error' });
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkConnection = async () => {
|
const checkConnection = async () => {
|
||||||
setMessage({ text: 'Checking connection...', type: 'info' });
|
setMessage({ text: 'Checking connection...', type: 'info' });
|
||||||
let isConnect = false
|
let isConnect = false
|
||||||
try {
|
try {
|
||||||
isConnect = await checkConnectTcpApi()
|
isConnect = await checkConnectTcpApi()
|
||||||
} catch {
|
} catch {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnect) {
|
if (!isConnect) {
|
||||||
setMessage({ text: 'Connection bridge to TCP Server failed', type: 'error' })
|
setMessage({ text: 'Connection bridge to TCP Server failed', type: 'error' })
|
||||||
} else {
|
} else {
|
||||||
setMessage({ text: 'Connection OK!', type: 'success' });
|
setMessage({ text: 'Connection OK!', type: 'success' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleShow = (modalId: string) => {
|
const handleShow = (modalId: string) => {
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
modal.showModal();
|
modal.showModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close modal handler
|
// Close modal handler
|
||||||
const handleCloseModal = (modalId: string) => {
|
const handleCloseModal = (modalId: string) => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.close()
|
modal.close()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle ESC key to close modal
|
// Handle ESC key to close modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscKey = (event: KeyboardEvent) => {
|
const handleEscKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape' && isModalOpen) {
|
if (event.key === 'Escape' && isModalOpen) {
|
||||||
handleCloseModal("character_detail_modal");
|
handleCloseModal("character_detail_modal");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleEscKey);
|
window.addEventListener('keydown', handleEscKey);
|
||||||
return () => window.removeEventListener('keydown', handleEscKey);
|
return () => window.removeEventListener('keydown', handleEscKey);
|
||||||
}, [isModalOpen]);
|
}, [isModalOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="navbar bg-base-100 shadow-md sticky top-0 z-50 px-3 py-1">
|
<div className="navbar bg-base-100 shadow-md sticky top-0 z-50 px-3 py-1">
|
||||||
<div className="navbar-start">
|
<div className="navbar-start">
|
||||||
{/* Mobile menu dropdown */}
|
{/* Mobile menu dropdown */}
|
||||||
<div className="dropdown">
|
<div className="dropdown">
|
||||||
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm lg:hidden hover:bg-base-200 transition-all duration-300">
|
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm lg:hidden hover:bg-base-200 transition-all duration-300">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-10 mt-3 w-52 p-2 shadow-md border border-base-200"
|
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-10 mt-3 w-52 p-2 shadow-md border border-base-200"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/json"
|
accept="application/json"
|
||||||
id="battle-data-upload"
|
id="battle-data-upload"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
importBattleData(file, loadBattleDataFromJSON)
|
importBattleData(file, loadBattleDataFromJSON)
|
||||||
.then(() => console.log('Data loaded'))
|
.then(() => console.log('Data loaded'))
|
||||||
.catch(err => alert('Failed to load data: ' + err.message));
|
.catch(err => alert('Failed to load data: ' + err.message));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => document.getElementById('battle-data-upload')?.click()}
|
onClick={() => document.getElementById('battle-data-upload')?.click()}
|
||||||
>
|
>
|
||||||
{transI18n("loadData")}
|
{transI18n("loadData")}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => exportBattleData()}
|
onClick={() => exportBattleData()}
|
||||||
>
|
>
|
||||||
{transI18n("exportData")}
|
{transI18n("exportData")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => handleShow("socket_connection_modal")}
|
onClick={() => handleShow("socket_connection_modal")}
|
||||||
>
|
>
|
||||||
{transI18n("connectSetting")}
|
{transI18n("connectSetting")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
|
|
||||||
<a className="hidden sm:grid sm:grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
<a className="hidden sm:grid sm:grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<Image src="/ff-sranalysis.png" alt="Logo" width={50} height={50} />
|
<Image src="/ff-sranalysis.png" alt="Logo" width={50} height={50} />
|
||||||
<div className="flex flex-col justify-center items-start">
|
<div className="flex flex-col justify-center items-start">
|
||||||
<h1 className="text-xl font-bold">
|
<h1 className="text-xl font-bold">
|
||||||
<span className="text-emerald-500">Firefly Analy</span>
|
<span className="text-emerald-500">Firefly Analy</span>
|
||||||
<span className="bg-clip-text text-transparent bg-linear-to-r from-emerald-400 via-orange-500 to-red-500">
|
<span className="bg-clip-text text-transparent bg-linear-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||||
sis
|
sis
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-gray-500">For Veritas</p>
|
<p className="text-sm text-gray-500">For Veritas</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{version && (
|
{version && (
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<div className="inline-flex items-center space-x-2 px-3 py-1.5 bg-linear-to-r from-blue-500 to-purple-600 rounded-full shadow-md hover:shadow-lg transition-all duration-200">
|
<div className="inline-flex items-center space-x-2 px-3 py-1.5 bg-linear-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="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
|
||||||
<div className="text-xs font-semibold text-white">
|
<div className="text-xs font-semibold text-white">
|
||||||
{version}
|
{version}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop navigation */}
|
{/* Desktop navigation */}
|
||||||
<div className="navbar-center hidden lg:flex">
|
<div className="navbar-center hidden lg:flex">
|
||||||
<ul className="menu menu-horizontal gap-1">
|
<ul className="menu menu-horizontal gap-1">
|
||||||
<li>
|
<li>
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/json"
|
accept="application/json"
|
||||||
id="battle-data-upload"
|
id="battle-data-upload"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
importBattleData(file, loadBattleDataFromJSON)
|
importBattleData(file, loadBattleDataFromJSON)
|
||||||
.then(() => console.log('Data loaded'))
|
.then(() => console.log('Data loaded'))
|
||||||
.catch(err => alert('Failed to load data: ' + err.message));
|
.catch(err => alert('Failed to load data: ' + err.message));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => document.getElementById('battle-data-upload')?.click()}
|
onClick={() => document.getElementById('battle-data-upload')?.click()}
|
||||||
>
|
>
|
||||||
{transI18n("loadData")}
|
{transI18n("loadData")}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => exportBattleData()}
|
onClick={() => exportBattleData()}
|
||||||
>
|
>
|
||||||
{transI18n("exportData")}
|
{transI18n("exportData")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||||
onClick={() => handleShow("socket_connection_modal")}
|
onClick={() => handleShow("socket_connection_modal")}
|
||||||
>
|
>
|
||||||
{transI18n("connectSetting")}
|
{transI18n("connectSetting")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Right side items */}
|
{/* Right side items */}
|
||||||
<div className="navbar-end gap-2">
|
<div className="navbar-end gap-2">
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md">
|
<div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md">
|
||||||
<div className={`hidden lg:block text-sm italic ${status ? 'text-green-500' : 'text-red-500'}`}>
|
<div className={`hidden lg:block text-sm italic ${status ? 'text-green-500' : 'text-red-500'}`}>
|
||||||
{status ? transI18n("connected") : transI18n("unconnected")}
|
{status ? transI18n("connected") : transI18n("unconnected")}
|
||||||
</div>
|
</div>
|
||||||
<div className={`w-3 h-3 rounded-full ${status ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
<div className={`w-3 h-3 rounded-full ${status ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Language selector - REFINED */}
|
{/* Language selector - REFINED */}
|
||||||
<div className="dropdown dropdown-end">
|
<div className="dropdown dropdown-end">
|
||||||
<div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200">
|
<div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-4">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
className="outline-none bg-base-200 cursor-pointer text-sm pr-0"
|
className="outline-none bg-base-200 cursor-pointer text-sm pr-0"
|
||||||
value={locale}
|
value={locale}
|
||||||
onChange={(e) => changeLocale(e.target.value)}
|
onChange={(e) => changeLocale(e.target.value)}
|
||||||
>
|
>
|
||||||
{Object.entries(listCurrentLanguage).map(([key, value]) => (
|
{Object.entries(listCurrentLanguage).map(([key, value]) => (
|
||||||
<option key={key} value={key}>{value}</option>
|
<option key={key} value={key}>{value}</option>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div title="Change Theme" className="dropdown dropdown-end">
|
<div title="Change Theme" className="dropdown dropdown-end">
|
||||||
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm hover:bg-base-200 transition-all duration-200 px-2">
|
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm hover:bg-base-200 transition-all duration-200 px-2">
|
||||||
<svg
|
<svg
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
className="h-4 w-4 stroke-current"
|
className="h-4 w-4 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg
|
<svg
|
||||||
width="10px"
|
width="10px"
|
||||||
height="10px"
|
height="10px"
|
||||||
className="ml-1 h-2 w-2 fill-current opacity-60"
|
className="ml-1 h-2 w-2 fill-current opacity-60"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 2048 2048"
|
viewBox="0 0 2048 2048"
|
||||||
>
|
>
|
||||||
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
|
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="dropdown-content bg-base-100 text-base-content rounded-lg top-px overflow-y-auto border border-base-200 shadow-md mt-12"
|
className="dropdown-content bg-base-100 text-base-content rounded-lg top-px overflow-y-auto border border-base-200 shadow-md mt-12"
|
||||||
>
|
>
|
||||||
<ul className="menu w-40 p-1">
|
<ul className="menu w-40 p-1">
|
||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<li
|
<li
|
||||||
key={theme.label}
|
key={theme.label}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (changeTheme) changeTheme(theme.label.toLowerCase());
|
if (changeTheme) changeTheme(theme.label.toLowerCase());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="gap-2 px-2 py-1.5 hover:bg-base-200 rounded text-sm transition-all duration-200"
|
className="gap-2 px-2 py-1.5 hover:bg-base-200 rounded text-sm transition-all duration-200"
|
||||||
data-set-theme={theme.label.toLowerCase()}
|
data-set-theme={theme.label.toLowerCase()}
|
||||||
data-act-class="[&_svg]:visible"
|
data-act-class="[&_svg]:visible"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-theme={theme.label.toLowerCase()}
|
data-theme={theme.label.toLowerCase()}
|
||||||
className="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded p-1 shadow-sm"
|
className="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded p-1 shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="bg-base-content size-1 rounded-full" />
|
<div className="bg-base-content size-1 rounded-full" />
|
||||||
<div className="bg-primary size-1 rounded-full" />
|
<div className="bg-primary size-1 rounded-full" />
|
||||||
<div className="bg-secondary size-1 rounded-full" />
|
<div className="bg-secondary size-1 rounded-full" />
|
||||||
<div className="bg-accent size-1 rounded-full" />
|
<div className="bg-accent size-1 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm">{theme.label}</div>
|
<div className="text-sm">{theme.label}</div>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* GitHub Link */}
|
{/* GitHub Link */}
|
||||||
<Link
|
<Link
|
||||||
className='hidden sm:flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center'
|
className='hidden sm:flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center'
|
||||||
href={"https://github.com/AzenKain/SR-Analysis"}
|
href={"https://github.com/AzenKain/SR-Analysis"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dialog id="socket_connection_modal" className="modal sm:modal-middle backdrop-blur-sm">
|
<dialog id="socket_connection_modal" className="modal sm:modal-middle backdrop-blur-sm">
|
||||||
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||||
<div className="sticky top-0 z-10">
|
<div className="sticky top-0 z-10">
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
||||||
onClick={() => handleCloseModal("socket_connection_modal")}
|
onClick={() => handleCloseModal("socket_connection_modal")}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
|
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
|
||||||
{transI18n("socketConnection").toUpperCase()}
|
{transI18n("socketConnection").toUpperCase()}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 py-4">
|
<div className="px-6 py-4">
|
||||||
{/* Select connection type */}
|
{/* Select connection type */}
|
||||||
<div className="form-control grid grid-cols-1 w-full mb-6">
|
<div className="form-control grid grid-cols-1 w-full mb-6">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span>
|
<span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
className="select select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200"
|
className="select select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200"
|
||||||
value={connectionType}
|
value={connectionType}
|
||||||
onChange={(e) => setConnectionType(e.target.value)}
|
onChange={(e) => setConnectionType(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="Native">Native</option>
|
<option value="Native">Native</option>
|
||||||
<option value="PS">PS</option>
|
<option value="PS">PS</option>
|
||||||
<option value="Other">{transI18n("other")}</option>
|
<option value="Other">{transI18n("other")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Show host/port if Other */}
|
{/* Show host/port if Other */}
|
||||||
{connectionType === "Other" && (
|
{connectionType === "Other" && (
|
||||||
<div className="flex flex-col md:flex-row md:space-x-4 mb-6">
|
<div className="flex flex-col md:flex-row md:space-x-4 mb-6">
|
||||||
<div className="form-control w-full mb-4 md:mb-0">
|
<div className="form-control w-full mb-4 md:mb-0">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold text-purple-300">{transI18n("host")}</span>
|
<span className="label-text font-semibold text-purple-300">{transI18n("host")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={transI18n("hostPlaceHolder")}
|
placeholder={transI18n("hostPlaceHolder")}
|
||||||
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200"
|
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200"
|
||||||
value={host}
|
value={host}
|
||||||
onChange={(e) => setHost(e.target.value)}
|
onChange={(e) => setHost(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-control w-full">
|
<div className="form-control w-full">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold text-purple-300">{transI18n("port")}</span>
|
<span className="label-text font-semibold text-purple-300">{transI18n("port")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={transI18n("portPlaceHolder")}
|
placeholder={transI18n("portPlaceHolder")}
|
||||||
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200"
|
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200"
|
||||||
value={port || ''}
|
value={port || ''}
|
||||||
onChange={(e) => setPort(parseInt(e.target.value) || 0)}
|
onChange={(e) => setPort(parseInt(e.target.value) || 0)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
<div className={`alert ${message.type === 'success' ? 'alert-success' :
|
<div className={`alert ${message.type === 'success' ? 'alert-success' :
|
||||||
message.type === 'error' ? 'alert-error' : 'alert-info'
|
message.type === 'error' ? 'alert-error' : 'alert-info'
|
||||||
} mb-6`}>
|
} mb-6`}>
|
||||||
<span>{message.text}</span>
|
<span>{message.text}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-center mt-6 mb-2">
|
<div className="flex flex-col sm:flex-row justify-between items-center mt-6 mb-2">
|
||||||
<div className="mb-4 sm:mb-0">
|
<div className="mb-4 sm:mb-0">
|
||||||
<span className="text-md mr-2">{transI18n("status")}:</span>
|
<span className="text-md mr-2">{transI18n("status")}:</span>
|
||||||
<span className={`badge ${status ? 'badge-success' : 'badge-error'} badge-lg`}>
|
<span className={`badge ${status ? 'badge-success' : 'badge-error'} badge-lg`}>
|
||||||
{status ? transI18n("connected") : transI18n("unconnected")}
|
{status ? transI18n("connected") : transI18n("unconnected")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<button
|
<button
|
||||||
className={`btn btn-primary`}
|
className={`btn btn-primary`}
|
||||||
onClick={handleConnect}
|
onClick={handleConnect}
|
||||||
>
|
>
|
||||||
{transI18n("connect")}
|
{transI18n("connect")}
|
||||||
</button>
|
</button>
|
||||||
{connectionType !== "Native" && (
|
{connectionType !== "Native" && (
|
||||||
<button
|
<button
|
||||||
className={`btn btn-secondary`}
|
className={`btn btn-secondary`}
|
||||||
onClick={checkConnection}
|
onClick={checkConnection}
|
||||||
>
|
>
|
||||||
{transI18n("checkGameConnect")}
|
{transI18n("checkGameConnect")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+306
-306
@@ -1,307 +1,307 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import useAvatarDataStore from "@/stores/avatarDataStore";
|
import useAvatarDataStore from "@/stores/avatarDataStore";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import CharacterCard from "../card/characterCard";
|
import CharacterCard from "../card/characterCard";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { CharacterBasic } from "@/types";
|
import { CharacterBasic } from "@/types";
|
||||||
import useLocaleStore from "@/stores/localeStore";
|
import useLocaleStore from "@/stores/localeStore";
|
||||||
import { getNameChar } from '@/helper/getNameChar';
|
import { getNameChar } from '@/helper/getNameChar';
|
||||||
import SkillBarChart from "../chart/skillBarChart";
|
import SkillBarChart from "../chart/skillBarChart";
|
||||||
import SkillPieChart from "../chart/skillPieChart";
|
import SkillPieChart from "../chart/skillPieChart";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { DamageLineForOne } from "../chart/damageLineForOne";
|
import { DamageLineForOne } from "../chart/damageLineForOne";
|
||||||
import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne";
|
import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne";
|
||||||
import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData";
|
import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import NameAvatar from "../nameAvatar";
|
import NameAvatar from "../nameAvatar";
|
||||||
// import ShowCaseInfo from "../card/showCaseCard";
|
// import ShowCaseInfo from "../card/showCaseCard";
|
||||||
|
|
||||||
export default function LineupBar() {
|
export default function LineupBar() {
|
||||||
const [selectedCharacter, setSelectedCharacter] = useState<CharacterBasic | undefined>(undefined);
|
const [selectedCharacter, setSelectedCharacter] = useState<CharacterBasic | undefined>(undefined);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
const { lineup, turnHistory, dataAvatar } = useBattleDataStore();
|
const { lineup, turnHistory, dataAvatar } = useBattleDataStore();
|
||||||
const { listAvatar, mapAvatar } = useAvatarDataStore();
|
const { listAvatar, mapAvatar } = useAvatarDataStore();
|
||||||
const { locale } = useLocaleStore();
|
const { locale } = useLocaleStore();
|
||||||
const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0);
|
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 =>
|
const lineupAvatars = listAvatar?.filter(item =>
|
||||||
lineup?.some(av => av?.avatarId?.toString() === item.id)
|
lineup?.some(av => av?.avatarId?.toString() === item.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleShow = (modalId: string, item: CharacterBasic) => {
|
const handleShow = (modalId: string, item: CharacterBasic) => {
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
setSelectedCharacter(item);
|
setSelectedCharacter(item);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
modal.showModal();
|
modal.showModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close modal handler
|
// Close modal handler
|
||||||
const handleCloseModal = (modalId: string) => {
|
const handleCloseModal = (modalId: string) => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setSelectedCharacter(undefined);
|
setSelectedCharacter(undefined);
|
||||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.close()
|
modal.close()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle ESC key to close modal
|
// Handle ESC key to close modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscKey = (event: KeyboardEvent) => {
|
const handleEscKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape' && isModalOpen) {
|
if (event.key === 'Escape' && isModalOpen) {
|
||||||
handleCloseModal("character_detail_modal");
|
handleCloseModal("character_detail_modal");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleEscKey);
|
window.addEventListener('keydown', handleEscKey);
|
||||||
return () => window.removeEventListener('keydown', handleEscKey);
|
return () => window.removeEventListener('keydown', handleEscKey);
|
||||||
}, [isModalOpen]);
|
}, [isModalOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
|
<div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
className="text-center text-xl lg:text-2xl pb-2 font-bold text-transparent bg-clip-text bg-linear-to-r from-pink-500 via-purple-500 to-cyan-500"
|
className="text-center text-xl lg:text-2xl pb-2 font-bold text-transparent bg-clip-text bg-linear-to-r from-pink-500 via-purple-500 to-cyan-500"
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
{transI18n("lineupInfo")}
|
{transI18n("lineupInfo")}
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<div className="flex justify-center mb-2">
|
<div className="flex justify-center mb-2">
|
||||||
<div className="badge badge-accent gap-2 text-lg items-center px-4 py-2 whitespace-nowrap max-w-full">
|
<div className="badge badge-accent gap-2 text-lg items-center px-4 py-2 whitespace-nowrap max-w-full">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 shrink-0">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 shrink-0">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<span className="text-sm truncate flex flex-row">
|
<span className="text-sm truncate flex flex-row">
|
||||||
<div>
|
<div>
|
||||||
{transI18n("lastTurn")}:
|
{transI18n("lastTurn")}:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
locale={locale}
|
locale={locale}
|
||||||
text={getNameChar(locale, transI18n, mapAvatar?.[turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString() || ""])}
|
text={getNameChar(locale, transI18n, mapAvatar?.[turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString() || ""])}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="relative h-full pt-2 max-h-[90vh] lg:max-h-[80vh] border-t-2 border-accent">
|
<div className="relative h-full pt-2 max-h-[90vh] lg:max-h-[80vh] border-t-2 border-accent">
|
||||||
{lineupAvatars.length === 0 ? (
|
{lineupAvatars.length === 0 ? (
|
||||||
<div className="h-full flex justify-center items-start">
|
<div className="h-full flex justify-center items-start">
|
||||||
<p className="text-base-content opacity-50">{transI18n("noCharactersInLineup")}</p>
|
<p className="text-base-content opacity-50">{transI18n("noCharactersInLineup")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full overflow-x-auto md:overflow-x-hidden md:overflow-y-auto rounded-lg">
|
<div className="h-full w-full overflow-x-auto md:overflow-x-hidden md:overflow-y-auto rounded-lg">
|
||||||
<div className="flex flex-nowrap md:grid md:grid-cols-1 w-fit md:w-full justify-items-center items-start gap-2">
|
<div className="flex flex-nowrap md:grid md:grid-cols-1 w-fit md:w-full justify-items-center items-start gap-2">
|
||||||
{lineupAvatars?.map((item, index) => {
|
{lineupAvatars?.map((item, index) => {
|
||||||
const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1;
|
const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1;
|
||||||
const isLastTurn = item.id === lastTurnAvatarId.toString();
|
const isLastTurn = item.id === lastTurnAvatarId.toString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
onClick={() => handleShow("character_detail_modal", item)}
|
onClick={() => handleShow("character_detail_modal", item)}
|
||||||
className={`cursor-pointer shrink-0 justify-items-center ${isLastTurn ? "shadow-[inset_0_0_10px_2px_rgba(59,130,246,0.7),0_0_20px_5px_rgba(59,130,246,0.3)]" : ""
|
className={`cursor-pointer shrink-0 justify-items-center ${isLastTurn ? "shadow-[inset_0_0_10px_2px_rgba(59,130,246,0.7),0_0_20px_5px_rgba(59,130,246,0.3)]" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CharacterCard data={item} />
|
<CharacterCard data={item} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Character Detail Modal */}
|
{/* Character Detail Modal */}
|
||||||
<dialog id="character_detail_modal" className="modal backdrop-blur-sm">
|
<dialog id="character_detail_modal" className="modal backdrop-blur-sm">
|
||||||
<div className="modal-box w-11/12 max-w-7xl bg-linear-to-b bg-base-100 text-base-content border-purple-500 shadow-lg shadow-purple-500/20">
|
<div className="modal-box w-11/12 max-w-7xl bg-linear-to-b bg-base-100 text-base-content border-purple-500 shadow-lg shadow-purple-500/20">
|
||||||
<div className="sticky top-0 z-10">
|
<div className="sticky top-0 z-10">
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
|
||||||
onClick={() => handleCloseModal("character_detail_modal")}
|
onClick={() => handleCloseModal("character_detail_modal")}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
|
|
||||||
<NameAvatar
|
<NameAvatar
|
||||||
locale={locale}
|
locale={locale}
|
||||||
text={getNameChar(locale, transI18n, selectedCharacter).toUpperCase()}
|
text={getNameChar(locale, transI18n, selectedCharacter).toUpperCase()}
|
||||||
className={"font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"}
|
className={"font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedCharacter && (
|
{selectedCharacter && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="grid grid-cols-1 md:grid-cols-2 gap-6 p-2"
|
className="grid grid-cols-1 md:grid-cols-2 gap-6 p-2"
|
||||||
>
|
>
|
||||||
<div className="md:col-span-2 bg-base-200 rounded-lg p-4 shadow-md">
|
<div className="md:col-span-2 bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2">
|
<div className="grid grid-cols-1 sm:grid-cols-2">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-start items-center gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-start items-center gap-6">
|
||||||
<p>
|
<p>
|
||||||
{transI18n("id")}: <span className="font-bold">{selectedCharacter.id}</span>
|
{transI18n("id")}: <span className="font-bold">{selectedCharacter.id}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="flex items-center space-x-2">
|
<p className="flex items-center space-x-2">
|
||||||
<span>{transI18n("path")}:</span>
|
<span>{transI18n("path")}:</span>
|
||||||
<span className="font-bold">{transI18n(selectedCharacter.baseType.toLowerCase())}</span>
|
<span className="font-bold">{transI18n(selectedCharacter.baseType.toLowerCase())}</span>
|
||||||
{selectedCharacter.baseType && (
|
{selectedCharacter.baseType && (
|
||||||
<Image
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`/icon/${selectedCharacter.baseType.toLowerCase()}.webp`}
|
src={`/icon/${selectedCharacter.baseType.toLowerCase()}.webp`}
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
alt={selectedCharacter.baseType.toLowerCase()}
|
alt={selectedCharacter.baseType.toLowerCase()}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{transI18n("rarity")}: <span className="font-bold">{selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"}</span>
|
{transI18n("rarity")}: <span className="font-bold">{selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="flex items-center space-x-2">
|
<p className="flex items-center space-x-2">
|
||||||
<span>{transI18n("element")}:</span>
|
<span>{transI18n("element")}:</span>
|
||||||
<Image
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`/icon/${selectedCharacter.damageType.toLowerCase()}.webp`}
|
src={`/icon/${selectedCharacter.damageType.toLowerCase()}.webp`}
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
alt={selectedCharacter.damageType.toLowerCase()}
|
alt={selectedCharacter.damageType.toLowerCase()}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id);
|
const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id);
|
||||||
if (!avatar) return null;
|
if (!avatar) return null;
|
||||||
const relicIds = avatar.relics.map(item => {
|
const relicIds = avatar.relics.map(item => {
|
||||||
const relicIdStr = item.relic_id.toString().slice(1);
|
const relicIdStr = item.relic_id.toString().slice(1);
|
||||||
const slot = relicIdStr.slice(-1);
|
const slot = relicIdStr.slice(-1);
|
||||||
return `${item.relic_set_id}_${slot}`;
|
return `${item.relic_set_id}_${slot}`;
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
{transI18n("level")}: <span className="font-bold">{avatar.level}</span>
|
{transI18n("level")}: <span className="font-bold">{avatar.level}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{transI18n("eidolons")}: <span className="font-bold">{avatar?.data?.rank}</span>
|
{transI18n("eidolons")}: <span className="font-bold">{avatar?.data?.rank}</span>
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<span>{transI18n("lightcones")}:</span>
|
<span>{transI18n("lightcones")}:</span>
|
||||||
<Image
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`${process.env.CDN_URL}/spriteoutput/lightconemediumicon/${avatar?.Lightcone?.item_id}.png`}
|
src={`${process.env.CDN_URL}/spriteoutput/lightconemediumicon/${avatar?.Lightcone?.item_id}.png`}
|
||||||
className="w-12 h-12"
|
className="w-12 h-12"
|
||||||
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
||||||
width={200}
|
width={200}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2 w-full">
|
<div className="flex items-center space-x-2 w-full">
|
||||||
<span>{transI18n("relics")}:</span>
|
<span>{transI18n("relics")}:</span>
|
||||||
<div className="grid grid-cols-3 md:flex md:flex-row w-full">
|
<div className="grid grid-cols-3 md:flex md:flex-row w-full">
|
||||||
{relicIds.map(it => (
|
{relicIds.map(it => (
|
||||||
<Image
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
key={it}
|
key={it}
|
||||||
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${it}.png`}
|
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${it}.png`}
|
||||||
className="w-12 h-12"
|
className="w-12 h-12"
|
||||||
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
||||||
width={200}
|
width={200}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Image
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src={`${process.env.CDN_URL}/${selectedCharacter.icon}`}
|
src={`${process.env.CDN_URL}/${selectedCharacter.icon}`}
|
||||||
alt={getNameChar(locale, transI18n, selectedCharacter)}
|
alt={getNameChar(locale, transI18n, selectedCharacter)}
|
||||||
className="h-32 w-32 object-cover rounded-full border-2 border-purple-500"
|
className="h-32 w-32 object-cover rounded-full border-2 border-purple-500"
|
||||||
width={128}
|
width={128}
|
||||||
height={128}
|
height={128}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="md:col-span-2 bg-base-200 rounded-lg p-4 shadow-md">
|
{/* <div className="md:col-span-2 bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||||
<ShowCaseInfo></ShowCaseInfo>
|
<ShowCaseInfo></ShowCaseInfo>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<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">{Number(totalTurn).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</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>
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<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">{Number(totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</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>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<h4 className="text-lg font-semibold mb-4 text-purple-500">{transI18n("skillDamageBreakdown")}</h4>
|
<h4 className="text-lg font-semibold mb-4 text-purple-500">{transI18n("skillDamageBreakdown")}</h4>
|
||||||
<SkillBarChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
<SkillBarChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("damageOverTime")}</h4>
|
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("damageOverTime")}</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DamageLineForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
<DamageLineForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("skillUsageDistribution")}</h4>
|
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("skillUsageDistribution")}</h4>
|
||||||
<SkillPieChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
<SkillPieChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h4 className="text-lg font-semibold text-purple-500">{transI18n("damagePerCycle")}</h4>
|
<h4 className="text-lg font-semibold text-purple-500">{transI18n("damagePerCycle")}</h4>
|
||||||
</div>
|
</div>
|
||||||
<DamagePerCycleForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
<DamagePerCycleForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { parseRuby } from "@/helper";
|
import { parseRuby } from "@/helper";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface TextProps {
|
interface TextProps {
|
||||||
text: string;
|
text: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NameAvatar({ text, locale, className }: TextProps) {
|
export default function NameAvatar({ text, locale, className }: TextProps) {
|
||||||
if (locale === "ja") {
|
if (locale === "ja") {
|
||||||
return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />;
|
return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />;
|
||||||
}
|
}
|
||||||
return <div className={className}>{text}</div>;
|
return <div className={className}>{text}</div>;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { PropsWithChildren, useContext } from "react";
|
import { PropsWithChildren, useContext } from "react";
|
||||||
import { ThemeContext } from "./themeContext";
|
import { ThemeContext } from "./themeContext";
|
||||||
|
|
||||||
export function ClientThemeWrapper({ children }: PropsWithChildren) {
|
export function ClientThemeWrapper({ children }: PropsWithChildren) {
|
||||||
const { theme } = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
return <div data-theme={theme} className="h-full">{children}</div>;
|
return <div data-theme={theme} className="h-full">{children}</div>;
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from "./clientThemeWrapper"
|
export * from "./clientThemeWrapper"
|
||||||
export * from "./themeContext"
|
export * from "./themeContext"
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import useLocaleStore from "@/stores/localeStore";
|
import useLocaleStore from "@/stores/localeStore";
|
||||||
import { createContext, PropsWithChildren } from "react";
|
import { createContext, PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface ThemeContextType {
|
interface ThemeContextType {
|
||||||
theme?: string;
|
theme?: string;
|
||||||
changeTheme?: (nextTheme: string | null) => void;
|
changeTheme?: (nextTheme: string | null) => void;
|
||||||
}
|
}
|
||||||
export const ThemeContext = createContext<ThemeContextType>({});
|
export const ThemeContext = createContext<ThemeContextType>({});
|
||||||
|
|
||||||
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||||
const { theme, setTheme } = useLocaleStore()
|
const { theme, setTheme } = useLocaleStore()
|
||||||
|
|
||||||
const changeTheme = (nextTheme: string | null) => {
|
const changeTheme = (nextTheme: string | null) => {
|
||||||
if (nextTheme) {
|
if (nextTheme) {
|
||||||
setTheme(nextTheme);
|
setTheme(nextTheme);
|
||||||
} else {
|
} else {
|
||||||
setTheme(theme === "winter" ? "night" : "winter");
|
setTheme(theme === "winter" ? "night" : "winter");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{ theme, changeTheme }}>
|
<ThemeContext.Provider value={{ theme, changeTheme }}>
|
||||||
{children}
|
{children}
|
||||||
</ThemeContext.Provider>
|
</ThemeContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Vendored
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
declare module '*.css' {
|
declare module '*.css' {
|
||||||
const content: { [className: string]: string };
|
const content: { [className: string]: string };
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,48 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { BattleDataStateJson } from "@/types/mics";
|
import { BattleDataStateJson } from "@/types/mics";
|
||||||
|
|
||||||
export const exportBattleData = (
|
export const exportBattleData = (
|
||||||
filename = 'battle_data.json'
|
filename = 'battle_data.json'
|
||||||
) => {
|
) => {
|
||||||
const { lineup,
|
const { lineup,
|
||||||
turnHistory,
|
turnHistory,
|
||||||
skillHistory,
|
skillHistory,
|
||||||
totalAV,
|
totalAV,
|
||||||
totalDamage,
|
totalDamage,
|
||||||
damagePerAV,
|
damagePerAV,
|
||||||
cycleIndex,
|
cycleIndex,
|
||||||
waveIndex,
|
waveIndex,
|
||||||
dataAvatar,
|
dataAvatar,
|
||||||
maxWave,
|
maxWave,
|
||||||
maxCycle,
|
maxCycle,
|
||||||
version,
|
version,
|
||||||
avatarDetail,
|
avatarDetail,
|
||||||
enemyDetail
|
enemyDetail
|
||||||
} = useBattleDataStore.getState();
|
} = useBattleDataStore.getState();
|
||||||
|
|
||||||
const data: BattleDataStateJson = {
|
const data: BattleDataStateJson = {
|
||||||
lineup,
|
lineup,
|
||||||
turnHistory,
|
turnHistory,
|
||||||
skillHistory,
|
skillHistory,
|
||||||
dataAvatar,
|
dataAvatar,
|
||||||
totalAV,
|
totalAV,
|
||||||
totalDamage,
|
totalDamage,
|
||||||
damagePerAV,
|
damagePerAV,
|
||||||
cycleIndex,
|
cycleIndex,
|
||||||
waveIndex,
|
waveIndex,
|
||||||
maxWave,
|
maxWave,
|
||||||
maxCycle,
|
maxCycle,
|
||||||
version,
|
version,
|
||||||
avatarDetail,
|
avatarDetail,
|
||||||
enemyDetail
|
enemyDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataStr = JSON.stringify(data, null, 2);
|
const dataStr = JSON.stringify(data, null, 2);
|
||||||
const blob = new Blob([dataStr], { type: 'application/json' });
|
const blob = new Blob([dataStr], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = filename;
|
a.download = filename;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
+62
-62
@@ -1,63 +1,63 @@
|
|||||||
import { listCurrentLanguage } from "@/lib/constant";
|
import { listCurrentLanguage } from "@/lib/constant";
|
||||||
import { CharacterBasic, MonsterBasic } from "@/types";
|
import { CharacterBasic, MonsterBasic } from "@/types";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
|
||||||
type TFunc = ReturnType<typeof useTranslations>
|
type TFunc = ReturnType<typeof useTranslations>
|
||||||
|
|
||||||
function cleanText(text: string): string {
|
function cleanText(text: string): string {
|
||||||
if (!text) return ""
|
if (!text) return ""
|
||||||
return text.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1")
|
return text.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNameChar(
|
export function getNameChar(
|
||||||
locale: string,
|
locale: string,
|
||||||
t: TFunc,
|
t: TFunc,
|
||||||
data: CharacterBasic | undefined
|
data: CharacterBasic | undefined
|
||||||
): string {
|
): string {
|
||||||
if (!data) return "";
|
if (!data) return "";
|
||||||
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
|
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
||||||
|
|
||||||
let text = data.lang[langKey] ?? "";
|
let text = data.lang[langKey] ?? "";
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
text = data.lang["en"] ?? "";
|
text = data.lang["en"] ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(data.id) > 8000) {
|
if (Number(data.id) > 8000) {
|
||||||
text = `${t("trailblazer")} • ${t(data?.baseType?.toLowerCase() ?? "")}`;
|
text = `${t("trailblazer")} • ${t(data?.baseType?.toLowerCase() ?? "")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanText(text);
|
return cleanText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNameEnemy(locale: string, data: MonsterBasic | undefined): string {
|
export function getNameEnemy(locale: string, data: MonsterBasic | undefined): string {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
|
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
|
||||||
|
|
||||||
|
|
||||||
let text = data.lang[langKey] ?? "";
|
let text = data.lang[langKey] ?? "";
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
text = data.lang["en"] ?? "";
|
text = data.lang["en"] ?? "";
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseRuby(text: string): string {
|
export function parseRuby(text: string): string {
|
||||||
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
|
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
|
||||||
return text.replace(rubyRegex, (_match, furigana, kanji) => {
|
return text.replace(rubyRegex, (_match, furigana, kanji) => {
|
||||||
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
|
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
import { BattleDataStateJson } from "@/types/mics";
|
import { BattleDataStateJson } from "@/types/mics";
|
||||||
|
|
||||||
export const importBattleData = (file: File, loadBattleDataFromJSON: (data: BattleDataStateJson) => void) => {
|
export const importBattleData = (file: File, loadBattleDataFromJSON: (data: BattleDataStateJson) => void) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
try {
|
try {
|
||||||
const result = reader.result as string;
|
const result = reader.result as string;
|
||||||
const parsed = JSON.parse(result) as BattleDataStateJson;
|
const parsed = JSON.parse(result) as BattleDataStateJson;
|
||||||
loadBattleDataFromJSON(parsed);
|
loadBattleDataFromJSON(parsed);
|
||||||
resolve();
|
resolve();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.onerror = () => reject(reader.error);
|
reader.onerror = () => reject(reader.error);
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
export * from "./getNameChar"
|
export * from "./getNameChar"
|
||||||
export * from "./exportDataBattle"
|
export * from "./exportDataBattle"
|
||||||
export * from "./importDataBattle"
|
export * from "./importDataBattle"
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function useCalcTotalDmgAvatar(avatarId: number) {
|
export function useCalcTotalDmgAvatar(avatarId: number) {
|
||||||
const { skillHistory } = useBattleDataStore.getState();
|
const { skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return skillHistory
|
return skillHistory
|
||||||
.filter(t => t.avatarId === avatarId)
|
.filter(t => t.avatarId === avatarId)
|
||||||
.reduce((sum, turn) => sum + turn.totalDamage, 0);
|
.reduce((sum, turn) => sum + turn.totalDamage, 0);
|
||||||
}, [avatarId, skillHistory]);
|
}, [avatarId, skillHistory]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useCalcTotalTurnAvatar(avatarId: number) {
|
export function useCalcTotalTurnAvatar(avatarId: number) {
|
||||||
const { turnHistory } = useBattleDataStore.getState();
|
const { turnHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const uniqueActionValues = new Set<number>();
|
const uniqueActionValues = new Set<number>();
|
||||||
turnHistory.forEach(turn => {
|
turnHistory.forEach(turn => {
|
||||||
if (turn.avatarId === avatarId) {
|
if (turn.avatarId === avatarId) {
|
||||||
uniqueActionValues.add(turn.actionValue);
|
uniqueActionValues.add(turn.actionValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return uniqueActionValues.size;
|
return uniqueActionValues.size;
|
||||||
}, [avatarId, turnHistory]);
|
}, [avatarId, turnHistory]);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { ThemeContext } from "@/components/themeController";
|
import { ThemeContext } from "@/components/themeController";
|
||||||
|
|
||||||
export const useChangeTheme = () => useContext(ThemeContext);
|
export const useChangeTheme = () => useContext(ThemeContext);
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) {
|
export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) {
|
||||||
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const map = new Map<number, number>();
|
const map = new Map<number, number>();
|
||||||
|
|
||||||
for (const skill of skillHistory) {
|
for (const skill of skillHistory) {
|
||||||
if (skill.avatarId !== avatarId) continue;
|
if (skill.avatarId !== avatarId) continue;
|
||||||
const actionValue = turnHistory[skill.turnBattleId].actionValue
|
const actionValue = turnHistory[skill.turnBattleId].actionValue
|
||||||
const prev = map.get(actionValue) || 0;
|
const prev = map.get(actionValue) || 0;
|
||||||
map.set(actionValue, prev + skill.totalDamage);
|
map.set(actionValue, prev + skill.totalDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = Array.from(map.entries())
|
const points = Array.from(map.entries())
|
||||||
.map(([x, y]) => ({ x, y }))
|
.map(([x, y]) => ({ x, y }))
|
||||||
.sort((a, b) => a.x - b.x);
|
.sort((a, b) => a.x - b.x);
|
||||||
|
|
||||||
if (mode === 1) {
|
if (mode === 1) {
|
||||||
let cumulative = 0;
|
let cumulative = 0;
|
||||||
return points.map(p => {
|
return points.map(p => {
|
||||||
cumulative += p.y;
|
cumulative += p.y;
|
||||||
return { x: p.x, y: Number(cumulative.toFixed(2)) };
|
return { x: p.x, y: Number(cumulative.toFixed(2)) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return points.map(p => ({ x: p.x, y: Number(p.y.toFixed(2)) }));
|
return points.map(p => ({ x: p.x, y: Number(p.y.toFixed(2)) }));
|
||||||
}, [avatarId, skillHistory, turnHistory, mode]);
|
}, [avatarId, skillHistory, turnHistory, mode]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function useDamageLinesForAll(mode: 1 | 2 = 1) {
|
export function useDamageLinesForAll(mode: 1 | 2 = 1) {
|
||||||
const { turnHistory, skillHistory } = useBattleDataStore.getState();
|
const { turnHistory, skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const avatarMap = new Map<number, Map<number, number>>();
|
const avatarMap = new Map<number, Map<number, number>>();
|
||||||
|
|
||||||
for (const skill of skillHistory) {
|
for (const skill of skillHistory) {
|
||||||
if (!avatarMap.has(skill.avatarId)) {
|
if (!avatarMap.has(skill.avatarId)) {
|
||||||
avatarMap.set(skill.avatarId, new Map());
|
avatarMap.set(skill.avatarId, new Map());
|
||||||
}
|
}
|
||||||
|
|
||||||
const charMap = avatarMap.get(skill.avatarId)!;
|
const charMap = avatarMap.get(skill.avatarId)!;
|
||||||
const actionValue = turnHistory[skill.turnBattleId].actionValue
|
const actionValue = turnHistory[skill.turnBattleId].actionValue
|
||||||
const prev = charMap.get(actionValue) || 0;
|
const prev = charMap.get(actionValue) || 0;
|
||||||
charMap.set(actionValue, prev + skill.totalDamage);
|
charMap.set(actionValue, prev + skill.totalDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: Record<number, { x: number; y: number }[]> = {};
|
const result: Record<number, { x: number; y: number }[]> = {};
|
||||||
|
|
||||||
for (const [avatarId, damageMap] of avatarMap.entries()) {
|
for (const [avatarId, damageMap] of avatarMap.entries()) {
|
||||||
const points = Array.from(damageMap.entries())
|
const points = Array.from(damageMap.entries())
|
||||||
.map(([x, y]) => ({ x, y }))
|
.map(([x, y]) => ({ x, y }))
|
||||||
.sort((a, b) => a.x - b.x);
|
.sort((a, b) => a.x - b.x);
|
||||||
|
|
||||||
if (mode === 1) {
|
if (mode === 1) {
|
||||||
let cumulative = 0;
|
let cumulative = 0;
|
||||||
result[avatarId] = points.map(p => {
|
result[avatarId] = points.map(p => {
|
||||||
cumulative += Number(p.y);
|
cumulative += Number(p.y);
|
||||||
return { x: p.x, y: Number(cumulative.toFixed(2)) };
|
return { x: p.x, y: Number(cumulative.toFixed(2)) };
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result[avatarId] = points.map(p => ({
|
result[avatarId] = points.map(p => ({
|
||||||
x: p.x,
|
x: p.x,
|
||||||
y: Number(p.y.toFixed(2)),
|
y: Number(p.y.toFixed(2)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [turnHistory, skillHistory, mode]);
|
}, [turnHistory, skillHistory, mode]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,69 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
|
||||||
type Mode = 0 | 1 | 2;
|
type Mode = 0 | 1 | 2;
|
||||||
|
|
||||||
export function useDamagePerCycleForOne(avatarId: number, mode: Mode) {
|
export function useDamagePerCycleForOne(avatarId: number, mode: Mode) {
|
||||||
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const damageMap = new Map<string, number>();
|
const damageMap = new Map<string, number>();
|
||||||
|
|
||||||
skillHistory
|
skillHistory
|
||||||
.filter(s => s.avatarId === avatarId)
|
.filter(s => s.avatarId === avatarId)
|
||||||
.forEach(s => {
|
.forEach(s => {
|
||||||
const turn = turnHistory[s.turnBattleId];
|
const turn = turnHistory[s.turnBattleId];
|
||||||
if (!turn) return;
|
if (!turn) return;
|
||||||
|
|
||||||
let key = '';
|
let key = '';
|
||||||
if (mode === 0) {
|
if (mode === 0) {
|
||||||
key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`;
|
key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`;
|
||||||
} else if (mode === 1) {
|
} else if (mode === 1) {
|
||||||
key = `${transI18n('cycle')} ${turn.cycleIndex}`;
|
key = `${transI18n('cycle')} ${turn.cycleIndex}`;
|
||||||
} else if (mode === 2) {
|
} else if (mode === 2) {
|
||||||
key = `${transI18n('wave')} ${turn.waveIndex}`;
|
key = `${transI18n('wave')} ${turn.waveIndex}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage);
|
damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage);
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = Array.from(damageMap.entries())
|
const result = Array.from(damageMap.entries())
|
||||||
.map(([x, y]) => ({ x, y }))
|
.map(([x, y]) => ({ x, y }))
|
||||||
.sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true }));
|
.sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true }));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [avatarId, mode, skillHistory, turnHistory, transI18n]);
|
}, [avatarId, mode, skillHistory, turnHistory, transI18n]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useDamagePerCycleForAll(mode: Mode) {
|
export function useDamagePerCycleForAll(mode: Mode) {
|
||||||
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
const { skillHistory, turnHistory } = useBattleDataStore.getState();
|
||||||
const transI18n = useTranslations("DataAnalysisPage");
|
const transI18n = useTranslations("DataAnalysisPage");
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const damageMap = new Map<string, number>();
|
const damageMap = new Map<string, number>();
|
||||||
|
|
||||||
skillHistory.forEach(s => {
|
skillHistory.forEach(s => {
|
||||||
const turn = turnHistory[s.turnBattleId];
|
const turn = turnHistory[s.turnBattleId];
|
||||||
if (!turn) return;
|
if (!turn) return;
|
||||||
|
|
||||||
let key = '';
|
let key = '';
|
||||||
if (mode === 0) {
|
if (mode === 0) {
|
||||||
key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`;
|
key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`;
|
||||||
} else if (mode === 1) {
|
} else if (mode === 1) {
|
||||||
key = `${transI18n('cycle')} ${turn.cycleIndex}`;
|
key = `${transI18n('cycle')} ${turn.cycleIndex}`;
|
||||||
} else if (mode === 2) {
|
} else if (mode === 2) {
|
||||||
key = `${transI18n('wave')} ${turn.waveIndex}`;
|
key = `${transI18n('wave')} ${turn.waveIndex}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage);
|
damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage);
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = Array.from(damageMap.entries())
|
const result = Array.from(damageMap.entries())
|
||||||
.map(([x, y]) => ({ x, y }))
|
.map(([x, y]) => ({ x, y }))
|
||||||
.sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true }));
|
.sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true }));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [mode, skillHistory, turnHistory, transI18n]);
|
}, [mode, skillHistory, turnHistory, transI18n]);
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import {attackTypeToString} from "@/types";
|
import {attackTypeToString} from "@/types";
|
||||||
|
|
||||||
export function useDamagePercentByType() {
|
export function useDamagePercentByType() {
|
||||||
const { skillHistory } = useBattleDataStore.getState();
|
const { skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const dmgByType = new Map<string, number>();
|
const dmgByType = new Map<string, number>();
|
||||||
|
|
||||||
skillHistory.forEach(skill => {
|
skillHistory.forEach(skill => {
|
||||||
skill.damageDetail.forEach(detail => {
|
skill.damageDetail.forEach(detail => {
|
||||||
const type = detail.damage_type;
|
const type = detail.damage_type;
|
||||||
if (!type) return;
|
if (!type) return;
|
||||||
dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage);
|
dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalDmg = Array.from(dmgByType.values()).reduce((sum, val) => sum + val, 0);
|
const totalDmg = Array.from(dmgByType.values()).reduce((sum, val) => sum + val, 0);
|
||||||
|
|
||||||
return Array.from(dmgByType.entries()).map(([type, dmg]) => ({
|
return Array.from(dmgByType.entries()).map(([type, dmg]) => ({
|
||||||
type,
|
type,
|
||||||
percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0,
|
percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0,
|
||||||
}));
|
}));
|
||||||
}, [skillHistory]);
|
}, [skillHistory]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function useDamagePercentPerAvatar() {
|
export function useDamagePercentPerAvatar() {
|
||||||
const { skillHistory } = useBattleDataStore.getState();
|
const { skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const dmgByAvatar = new Map<number, number>();
|
const dmgByAvatar = new Map<number, number>();
|
||||||
|
|
||||||
skillHistory.forEach(t => {
|
skillHistory.forEach(t => {
|
||||||
dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage);
|
dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage);
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalDmg = Array.from(dmgByAvatar.values()).reduce((sum, v) => sum + v, 0);
|
const totalDmg = Array.from(dmgByAvatar.values()).reduce((sum, v) => sum + v, 0);
|
||||||
|
|
||||||
return Array.from(dmgByAvatar.entries()).map(([avatarId, dmg]) => ({
|
return Array.from(dmgByAvatar.entries()).map(([avatarId, dmg]) => ({
|
||||||
avatarId,
|
avatarId,
|
||||||
percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0,
|
percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0,
|
||||||
}));
|
}));
|
||||||
}, [skillHistory]);
|
}, [skillHistory]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import {attackTypeToString} from "@/types";
|
import {attackTypeToString} from "@/types";
|
||||||
|
|
||||||
export function useDamageByTypeForAvatar(avatarId: number) {
|
export function useDamageByTypeForAvatar(avatarId: number) {
|
||||||
const { skillHistory } = useBattleDataStore.getState();
|
const { skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const dmgMap = new Map<string, number>();
|
const dmgMap = new Map<string, number>();
|
||||||
|
|
||||||
for (const skill of skillHistory) {
|
for (const skill of skillHistory) {
|
||||||
if (skill.avatarId !== avatarId) continue;
|
if (skill.avatarId !== avatarId) continue;
|
||||||
|
|
||||||
for (const damage of skill.damageDetail) {
|
for (const damage of skill.damageDetail) {
|
||||||
const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || "";
|
const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || "";
|
||||||
dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage);
|
dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const labels = Array.from(dmgMap.keys());
|
const labels = Array.from(dmgMap.keys());
|
||||||
const damageValues = Array.from(dmgMap.values());
|
const damageValues = Array.from(dmgMap.values());
|
||||||
|
|
||||||
return { labels, damageValues };
|
return { labels, damageValues };
|
||||||
}, [avatarId, skillHistory]);
|
}, [avatarId, skillHistory]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import {attackTypeToString} from "@/types";
|
import {attackTypeToString} from "@/types";
|
||||||
|
|
||||||
export function useSkillDamageForAvatar(avatarId: number) {
|
export function useSkillDamageForAvatar(avatarId: number) {
|
||||||
const { skillHistory } = useBattleDataStore.getState();
|
const { skillHistory } = useBattleDataStore.getState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const dmgMap = new Map<string, number>();
|
const dmgMap = new Map<string, number>();
|
||||||
|
|
||||||
for (const skill of skillHistory) {
|
for (const skill of skillHistory) {
|
||||||
if (skill.avatarId !== avatarId) continue;
|
if (skill.avatarId !== avatarId) continue;
|
||||||
|
|
||||||
const type = attackTypeToString(skill.skillType).toLowerCase();
|
const type = attackTypeToString(skill.skillType).toLowerCase();
|
||||||
dmgMap.set(type, (dmgMap.get(type) || 0) + skill.totalDamage);
|
dmgMap.set(type, (dmgMap.get(type) || 0) + skill.totalDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const labels = Array.from(dmgMap.keys());
|
const labels = Array.from(dmgMap.keys());
|
||||||
const damageValues = Array.from(dmgMap.values());
|
const damageValues = Array.from(dmgMap.values());
|
||||||
|
|
||||||
return { labels, damageValues };
|
return { labels, damageValues };
|
||||||
}, [avatarId, skillHistory]);
|
}, [avatarId, skillHistory]);
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-51
@@ -1,51 +1,51 @@
|
|||||||
import useSocketStore from "@/stores/socketSettingStore";
|
import useSocketStore from "@/stores/socketSettingStore";
|
||||||
import { CharacterBasic, MonsterBasic } from "@/types";
|
import { CharacterBasic, MonsterBasic } from "@/types";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export async function checkConnectTcpApi(): Promise<boolean> {
|
export async function checkConnectTcpApi(): Promise<boolean> {
|
||||||
const { host, port, connectionType } = useSocketStore.getState()
|
const { host, port, connectionType } = useSocketStore.getState()
|
||||||
let url = `${host}:${port}/check-tcp`
|
let url = `${host}:${port}/check-tcp`
|
||||||
if (connectionType === "PS") {
|
if (connectionType === "PS") {
|
||||||
url = "http://localhost:21000/check-tcp"
|
url = "http://localhost:21000/check-tcp"
|
||||||
}
|
}
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.status
|
return data.status
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCharacterListApi(): Promise<CharacterBasic[]> {
|
export async function getCharacterListApi(): Promise<CharacterBasic[]> {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<CharacterBasic[]>("/api/data/avatar_basic");
|
const res = await axios.get<CharacterBasic[]>("/api/data/avatar_basic");
|
||||||
return res.data
|
return res.data
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Unexpected error: ${String(error)}`);
|
console.log(`Unexpected error: ${String(error)}`);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEnemyListApi(): Promise<MonsterBasic[]> {
|
export async function getEnemyListApi(): Promise<MonsterBasic[]> {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<MonsterBasic[]>("/api/data/monster_basic");
|
const res = await axios.get<MonsterBasic[]>("/api/data/monster_basic");
|
||||||
return res.data
|
return res.data
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
console.log(`Error: ${error.response?.status} - ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Unexpected error: ${String(error)}`);
|
console.log(`Unexpected error: ${String(error)}`);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+37
-37
@@ -1,38 +1,38 @@
|
|||||||
import { readFileSync, readdirSync } from "fs"
|
import { readFileSync, readdirSync } from "fs"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
type CacheItem = {
|
type CacheItem = {
|
||||||
buf: Uint8Array
|
buf: Uint8Array
|
||||||
type: "json" | "br"
|
type: "json" | "br"
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = new Map<string, CacheItem>()
|
const cache = new Map<string, CacheItem>()
|
||||||
|
|
||||||
const dir = path.join(process.cwd(), "data")
|
const dir = path.join(process.cwd(), "data")
|
||||||
|
|
||||||
for (const f of readdirSync(dir)) {
|
for (const f of readdirSync(dir)) {
|
||||||
const file = path.join(dir, f)
|
const file = path.join(dir, f)
|
||||||
|
|
||||||
if (f.endsWith(".json.br")) {
|
if (f.endsWith(".json.br")) {
|
||||||
const name = f.replace(".json.br", "")
|
const name = f.replace(".json.br", "")
|
||||||
const buf = new Uint8Array(readFileSync(file))
|
const buf = new Uint8Array(readFileSync(file))
|
||||||
cache.set(name, {
|
cache.set(name, {
|
||||||
buf,
|
buf,
|
||||||
type: "br"
|
type: "br"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.endsWith(".json")) {
|
if (f.endsWith(".json")) {
|
||||||
const name = f.replace(".json", "")
|
const name = f.replace(".json", "")
|
||||||
const buf = new Uint8Array(readFileSync(file))
|
const buf = new Uint8Array(readFileSync(file))
|
||||||
|
|
||||||
cache.set(name, {
|
cache.set(name, {
|
||||||
buf,
|
buf,
|
||||||
type: "json"
|
type: "json"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataCache(name: string) {
|
export function getDataCache(name: string) {
|
||||||
return cache.get(name)
|
return cache.get(name)
|
||||||
}
|
}
|
||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
export const listCurrentLanguage = {
|
export const listCurrentLanguage = {
|
||||||
ja: "JP",
|
ja: "JP",
|
||||||
ko: "KR",
|
ko: "KR",
|
||||||
en: "US",
|
en: "US",
|
||||||
vi: "VN",
|
vi: "VN",
|
||||||
zh: "CN"
|
zh: "CN"
|
||||||
};
|
};
|
||||||
+255
-255
@@ -1,255 +1,255 @@
|
|||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
import useSocketStore from "@/stores/socketSettingStore";
|
import useSocketStore from "@/stores/socketSettingStore";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import useBattleDataStore from "@/stores/battleDataStore";
|
import useBattleDataStore from "@/stores/battleDataStore";
|
||||||
import type {
|
import type {
|
||||||
AvatarAnalysisJson,
|
AvatarAnalysisJson,
|
||||||
BattleBeginType,
|
BattleBeginType,
|
||||||
BattleEndType,
|
BattleEndType,
|
||||||
DamageType,
|
DamageType,
|
||||||
EntityDefeatedType,
|
EntityDefeatedType,
|
||||||
InitializeEnemyType,
|
InitializeEnemyType,
|
||||||
SetBattleLineupType,
|
SetBattleLineupType,
|
||||||
StatChangeType,
|
StatChangeType,
|
||||||
TurnBeginType,
|
TurnBeginType,
|
||||||
TurnEndType,
|
TurnEndType,
|
||||||
UpdateCycleType,
|
UpdateCycleType,
|
||||||
UpdateTeamFormationType,
|
UpdateTeamFormationType,
|
||||||
UpdateWaveType,
|
UpdateWaveType,
|
||||||
UseSkillType,
|
UseSkillType,
|
||||||
VersionType,
|
VersionType,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
||||||
let socket: Socket | null = null;
|
let socket: Socket | null = null;
|
||||||
|
|
||||||
const notify = (msg: string, type: "info" | "success" | "error" = "info") => {
|
const notify = (msg: string, type: "info" | "success" | "error" = "info") => {
|
||||||
if (type === "success") toast.success(msg);
|
if (type === "success") toast.success(msg);
|
||||||
else if (type === "error") toast.error(msg);
|
else if (type === "error") toast.error(msg);
|
||||||
else toast.info(msg);
|
else toast.info(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SocketEvents {
|
interface SocketEvents {
|
||||||
Connected: VersionType;
|
Connected: VersionType;
|
||||||
OnBattleBegin: BattleBeginType;
|
OnBattleBegin: BattleBeginType;
|
||||||
OnSetBattleLineup: SetBattleLineupType;
|
OnSetBattleLineup: SetBattleLineupType;
|
||||||
OnDamage: DamageType;
|
OnDamage: DamageType;
|
||||||
OnTurnBegin: TurnBeginType;
|
OnTurnBegin: TurnBeginType;
|
||||||
OnTurnEnd: TurnEndType;
|
OnTurnEnd: TurnEndType;
|
||||||
OnEntityDefeated: EntityDefeatedType;
|
OnEntityDefeated: EntityDefeatedType;
|
||||||
OnUseSkill: UseSkillType;
|
OnUseSkill: UseSkillType;
|
||||||
OnUpdateWave: UpdateWaveType;
|
OnUpdateWave: UpdateWaveType;
|
||||||
OnUpdateCycle: UpdateCycleType;
|
OnUpdateCycle: UpdateCycleType;
|
||||||
OnStatChange: StatChangeType;
|
OnStatChange: StatChangeType;
|
||||||
OnUpdateTeamFormation: UpdateTeamFormationType;
|
OnUpdateTeamFormation: UpdateTeamFormationType;
|
||||||
OnInitializeEnemy: InitializeEnemyType;
|
OnInitializeEnemy: InitializeEnemyType;
|
||||||
OnBattleEnd: BattleEndType;
|
OnBattleEnd: BattleEndType;
|
||||||
OnCreateBattle: AvatarAnalysisJson[];
|
OnCreateBattle: AvatarAnalysisJson[];
|
||||||
Error: string;
|
Error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenerMap = {
|
type ListenerMap = {
|
||||||
[K in keyof SocketEvents]: (payload: SocketEvents[K]) => void;
|
[K in keyof SocketEvents]: (payload: SocketEvents[K]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
let listeners: ListenerMap | null = null;
|
let listeners: ListenerMap | null = null;
|
||||||
|
|
||||||
let workerPool: Worker[] | null = null;
|
let workerPool: Worker[] | null = null;
|
||||||
let nextWorker = 0;
|
let nextWorker = 0;
|
||||||
let reqId = 1;
|
let reqId = 1;
|
||||||
const pendingMap = new Map<number, (res: { parsed: unknown | null; err?: string }) => void>();
|
const pendingMap = new Map<number, (res: { parsed: unknown | null; err?: string }) => void>();
|
||||||
let workerBlobUrl: string | null = null;
|
let workerBlobUrl: string | null = null;
|
||||||
|
|
||||||
const createWorkerPool = (size = Math.max(1, (navigator.hardwareConcurrency || 4) - 1)) => {
|
const createWorkerPool = (size = Math.max(1, (navigator.hardwareConcurrency || 4) - 1)) => {
|
||||||
if (workerPool) return workerPool;
|
if (workerPool) return workerPool;
|
||||||
const code = `self.onmessage = function(e) { try { const { id, raw } = e.data; const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; self.postMessage({ id, parsed }); } catch (err) { self.postMessage({ id, err: err && err.message ? err.message : String(err) }); } }`;
|
const code = `self.onmessage = function(e) { try { const { id, raw } = e.data; const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; self.postMessage({ id, parsed }); } catch (err) { self.postMessage({ id, err: err && err.message ? err.message : String(err) }); } }`;
|
||||||
|
|
||||||
if (!workerBlobUrl) {
|
if (!workerBlobUrl) {
|
||||||
const blob = new Blob([code], { type: 'application/javascript' });
|
const blob = new Blob([code], { type: 'application/javascript' });
|
||||||
workerBlobUrl = URL.createObjectURL(blob);
|
workerBlobUrl = URL.createObjectURL(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
workerPool = Array.from({ length: size }).map(() => new Worker(workerBlobUrl!));
|
workerPool = Array.from({ length: size }).map(() => new Worker(workerBlobUrl!));
|
||||||
workerPool.forEach((w) => {
|
workerPool.forEach((w) => {
|
||||||
w.onmessage = (ev) => {
|
w.onmessage = (ev) => {
|
||||||
const { id, parsed, err } = ev.data as { id: number; parsed: unknown; err?: string };
|
const { id, parsed, err } = ev.data as { id: number; parsed: unknown; err?: string };
|
||||||
const cb = pendingMap.get(id);
|
const cb = pendingMap.get(id);
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb({ parsed: parsed ?? null, err });
|
cb({ parsed: parsed ?? null, err });
|
||||||
pendingMap.delete(id);
|
pendingMap.delete(id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
w.onerror = (ev) => {
|
w.onerror = (ev) => {
|
||||||
console.error('worker error', ev.message);
|
console.error('worker error', ev.message);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return workerPool;
|
return workerPool;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseWithWorker = (raw: unknown): Promise<{ parsed: unknown | null; err?: string }> => {
|
const parseWithWorker = (raw: unknown): Promise<{ parsed: unknown | null; err?: string }> => {
|
||||||
if (!workerPool) createWorkerPool();
|
if (!workerPool) createWorkerPool();
|
||||||
if (!workerPool) return Promise.resolve({ parsed: null, err: 'no worker' });
|
if (!workerPool) return Promise.resolve({ parsed: null, err: 'no worker' });
|
||||||
const id = reqId++;
|
const id = reqId++;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
pendingMap.delete(id);
|
pendingMap.delete(id);
|
||||||
resolve({ parsed: null, err: "worker timeout" });
|
resolve({ parsed: null, err: "worker timeout" });
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
pendingMap.set(id, (res) => {
|
pendingMap.set(id, (res) => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
resolve(res);
|
resolve(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
const w = workerPool![nextWorker];
|
const w = workerPool![nextWorker];
|
||||||
nextWorker = (nextWorker + 1) % workerPool!.length;
|
nextWorker = (nextWorker + 1) % workerPool!.length;
|
||||||
try {
|
try {
|
||||||
w.postMessage({ id, raw });
|
w.postMessage({ id, raw });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pendingMap.delete(id);
|
pendingMap.delete(id);
|
||||||
resolve({ parsed: null, err: err && (err as Error).message ? (err as Error).message : String(err) });
|
resolve({ parsed: null, err: err && (err as Error).message ? (err as Error).message : String(err) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let eventQueue: { name: keyof SocketEvents; data: unknown }[] = [];
|
let eventQueue: { name: keyof SocketEvents; data: unknown }[] = [];
|
||||||
let rafScheduled = false;
|
let rafScheduled = false;
|
||||||
const MAX_QUEUE = 1000;
|
const MAX_QUEUE = 1000;
|
||||||
|
|
||||||
const flushQueue = () => {
|
const flushQueue = () => {
|
||||||
const items = eventQueue.splice(0, eventQueue.length);
|
const items = eventQueue.splice(0, eventQueue.length);
|
||||||
if (items.length === 0) return;
|
if (items.length === 0) return;
|
||||||
const l = listeners;
|
const l = listeners;
|
||||||
if (!l) return;
|
if (!l) return;
|
||||||
for (const it of items) {
|
for (const it of items) {
|
||||||
try {
|
try {
|
||||||
l[it.name](it.data as never);
|
l[it.name](it.data as never);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('listener handler error', err);
|
console.error('listener handler error', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleFlush = () => {
|
const scheduleFlush = () => {
|
||||||
if (rafScheduled) return;
|
if (rafScheduled) return;
|
||||||
rafScheduled = true;
|
rafScheduled = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
rafScheduled = false;
|
rafScheduled = false;
|
||||||
flushQueue();
|
flushQueue();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const safeEnqueue = (name: keyof SocketEvents, data: unknown) => {
|
const safeEnqueue = (name: keyof SocketEvents, data: unknown) => {
|
||||||
if (eventQueue.length > MAX_QUEUE) {
|
if (eventQueue.length > MAX_QUEUE) {
|
||||||
eventQueue.shift();
|
eventQueue.shift();
|
||||||
}
|
}
|
||||||
eventQueue.push({ name, data });
|
eventQueue.push({ name, data });
|
||||||
scheduleFlush();
|
scheduleFlush();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const connectSocket = async (): Promise<Socket> => {
|
export const connectSocket = async (): Promise<Socket> => {
|
||||||
const { host, port, connectionType, setStatus } = useSocketStore.getState();
|
const { host, port, connectionType, setStatus } = useSocketStore.getState();
|
||||||
let url = `${host}:${port}`;
|
let url = `${host}:${port}`;
|
||||||
if (connectionType === "Native") url = "http://localhost:1305";
|
if (connectionType === "Native") url = "http://localhost:1305";
|
||||||
else if (connectionType === "PS") url = "http://localhost:21000";
|
else if (connectionType === "PS") url = "http://localhost:21000";
|
||||||
|
|
||||||
if (socket) socket.disconnect();
|
if (socket) socket.disconnect();
|
||||||
|
|
||||||
socket = io(url, {
|
socket = io(url, {
|
||||||
reconnectionAttempts: 5,
|
reconnectionAttempts: 5,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
autoConnect: true,
|
autoConnect: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
setStatus(true);
|
setStatus(true);
|
||||||
notify(`Connected: ${socket?.id}`, "success");
|
notify(`Connected: ${socket?.id}`, "success");
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("disconnect", () => setStatus(false));
|
socket.on("disconnect", () => setStatus(false));
|
||||||
socket.on("connect_error", () => setStatus(false));
|
socket.on("connect_error", () => setStatus(false));
|
||||||
socket.on("connect_timeout", () => setStatus(false));
|
socket.on("connect_timeout", () => setStatus(false));
|
||||||
socket.on("reconnect_failed", () => setStatus(false));
|
socket.on("reconnect_failed", () => setStatus(false));
|
||||||
|
|
||||||
listeners = {
|
listeners = {
|
||||||
Connected: (payload) => useBattleDataStore.getState().onConnectedService(payload),
|
Connected: (payload) => useBattleDataStore.getState().onConnectedService(payload),
|
||||||
OnBattleBegin: (payload) => {
|
OnBattleBegin: (payload) => {
|
||||||
notify("Battle Started!", "info");
|
notify("Battle Started!", "info");
|
||||||
useBattleDataStore.getState().onBattleBeginService(payload);
|
useBattleDataStore.getState().onBattleBeginService(payload);
|
||||||
},
|
},
|
||||||
OnSetBattleLineup: (payload) => useBattleDataStore.getState().onSetBattleLineupService(payload),
|
OnSetBattleLineup: (payload) => useBattleDataStore.getState().onSetBattleLineupService(payload),
|
||||||
OnDamage: (payload) => useBattleDataStore.getState().onDamageService(payload),
|
OnDamage: (payload) => useBattleDataStore.getState().onDamageService(payload),
|
||||||
OnTurnBegin: (payload) => useBattleDataStore.getState().onTurnBeginService(payload),
|
OnTurnBegin: (payload) => useBattleDataStore.getState().onTurnBeginService(payload),
|
||||||
OnTurnEnd: (payload) => useBattleDataStore.getState().onTurnEndService(payload),
|
OnTurnEnd: (payload) => useBattleDataStore.getState().onTurnEndService(payload),
|
||||||
OnEntityDefeated: (payload) => useBattleDataStore.getState().onEntityDefeatedService(payload),
|
OnEntityDefeated: (payload) => useBattleDataStore.getState().onEntityDefeatedService(payload),
|
||||||
OnUseSkill: (payload) => useBattleDataStore.getState().onUseSkillService(payload),
|
OnUseSkill: (payload) => useBattleDataStore.getState().onUseSkillService(payload),
|
||||||
OnUpdateWave: (payload) => useBattleDataStore.getState().onUpdateWaveService(payload),
|
OnUpdateWave: (payload) => useBattleDataStore.getState().onUpdateWaveService(payload),
|
||||||
OnUpdateCycle: (payload) => useBattleDataStore.getState().onUpdateCycleService(payload),
|
OnUpdateCycle: (payload) => useBattleDataStore.getState().onUpdateCycleService(payload),
|
||||||
OnStatChange: (payload) => useBattleDataStore.getState().onStatChange(payload),
|
OnStatChange: (payload) => useBattleDataStore.getState().onStatChange(payload),
|
||||||
OnUpdateTeamFormation: (payload) => useBattleDataStore.getState().onUpdateTeamFormation(payload),
|
OnUpdateTeamFormation: (payload) => useBattleDataStore.getState().onUpdateTeamFormation(payload),
|
||||||
OnInitializeEnemy: (payload) => useBattleDataStore.getState().onInitializeEnemyService(payload),
|
OnInitializeEnemy: (payload) => useBattleDataStore.getState().onInitializeEnemyService(payload),
|
||||||
OnBattleEnd: (payload) => useBattleDataStore.getState().onBattleEndService(payload),
|
OnBattleEnd: (payload) => useBattleDataStore.getState().onBattleEndService(payload),
|
||||||
OnCreateBattle: (payload) => useBattleDataStore.getState().onCreateBattleService(payload),
|
OnCreateBattle: (payload) => useBattleDataStore.getState().onCreateBattleService(payload),
|
||||||
Error: (msg) => console.error("Server Error:", msg),
|
Error: (msg) => console.error("Server Error:", msg),
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = (eventName: string) => {
|
const register = (eventName: string) => {
|
||||||
socket?.on(eventName, async (raw: unknown) => {
|
socket?.on(eventName, async (raw: unknown) => {
|
||||||
if (!listeners) return;
|
if (!listeners) return;
|
||||||
if (workerPool) {
|
if (workerPool) {
|
||||||
const res = await parseWithWorker(raw);
|
const res = await parseWithWorker(raw);
|
||||||
if (res.err) {
|
if (res.err) {
|
||||||
safeEnqueue("Error", `Worker parse error for ${eventName}: ${res.err}`);
|
safeEnqueue("Error", `Worker parse error for ${eventName}: ${res.err}`);
|
||||||
} else if (res.parsed !== null) {
|
} else if (res.parsed !== null) {
|
||||||
safeEnqueue(eventName as keyof SocketEvents, res.parsed);
|
safeEnqueue(eventName as keyof SocketEvents, res.parsed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
safeEnqueue(eventName as keyof SocketEvents, parsed);
|
safeEnqueue(eventName as keyof SocketEvents, parsed);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
safeEnqueue("Error", `Parse error for ${eventName}: ${err && (err as Error).message ? (err as Error).message : String(err)}`);
|
safeEnqueue("Error", `Parse error for ${eventName}: ${err && (err as Error).message ? (err as Error).message : String(err)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(listeners).forEach((k) => register(k));
|
Object.keys(listeners).forEach((k) => register(k));
|
||||||
|
|
||||||
createWorkerPool();
|
createWorkerPool();
|
||||||
|
|
||||||
return socket;
|
return socket;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disconnectSocket = (): void => {
|
export const disconnectSocket = (): void => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
if (listeners) {
|
if (listeners) {
|
||||||
Object.keys(listeners).forEach((eventName) => {
|
Object.keys(listeners).forEach((eventName) => {
|
||||||
socket?.off(eventName);
|
socket?.off(eventName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (workerBlobUrl) {
|
if (workerBlobUrl) {
|
||||||
URL.revokeObjectURL(workerBlobUrl);
|
URL.revokeObjectURL(workerBlobUrl);
|
||||||
workerBlobUrl = null;
|
workerBlobUrl = null;
|
||||||
}
|
}
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
useSocketStore.getState().setStatus(false);
|
useSocketStore.getState().setStatus(false);
|
||||||
listeners = null;
|
listeners = null;
|
||||||
if (workerPool) {
|
if (workerPool) {
|
||||||
workerPool.forEach((w) => w.terminate());
|
workerPool.forEach((w) => w.terminate());
|
||||||
workerPool = null;
|
workerPool = null;
|
||||||
}
|
}
|
||||||
pendingMap.clear();
|
pendingMap.clear();
|
||||||
eventQueue = [];
|
eventQueue = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isSocketConnected = (): boolean => socket?.connected || false;
|
export const isSocketConnected = (): boolean => socket?.connected || false;
|
||||||
export const getSocket = (): Socket | null => socket;
|
export const getSocket = (): Socket | null => socket;
|
||||||
|
|
||||||
export const setWorkerPoolSize = (size: number) => {
|
export const setWorkerPoolSize = (size: number) => {
|
||||||
if (workerPool) {
|
if (workerPool) {
|
||||||
workerPool.forEach((w) => w.terminate());
|
workerPool.forEach((w) => w.terminate());
|
||||||
workerPool = null;
|
workerPool = null;
|
||||||
}
|
}
|
||||||
createWorkerPool(Math.max(1, size));
|
createWorkerPool(Math.max(1, size));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import { CharacterBasic, MonsterBasic } from '@/types';
|
import { CharacterBasic, MonsterBasic } from '@/types';
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
|
||||||
interface AvatarDataState {
|
interface AvatarDataState {
|
||||||
listAvatar: CharacterBasic[];
|
listAvatar: CharacterBasic[];
|
||||||
mapAvatar: Record<string, CharacterBasic>;
|
mapAvatar: Record<string, CharacterBasic>;
|
||||||
listEnemy: MonsterBasic[];
|
listEnemy: MonsterBasic[];
|
||||||
mapEnemy: Record<string, MonsterBasic>;
|
mapEnemy: Record<string, MonsterBasic>;
|
||||||
setListAvatar: (list: CharacterBasic[]) => void;
|
setListAvatar: (list: CharacterBasic[]) => void;
|
||||||
setMapAvatar: (data: Record<string, CharacterBasic>) => void;
|
setMapAvatar: (data: Record<string, CharacterBasic>) => void;
|
||||||
setListEnemy: (list: MonsterBasic[]) => void;
|
setListEnemy: (list: MonsterBasic[]) => void;
|
||||||
setMapEnemy: (data: Record<string, MonsterBasic>) => void;
|
setMapEnemy: (data: Record<string, MonsterBasic>) => void;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAvatarDataStore = create<AvatarDataState>((set) => ({
|
const useAvatarDataStore = create<AvatarDataState>((set) => ({
|
||||||
listAvatar: [],
|
listAvatar: [],
|
||||||
listEnemy: [],
|
listEnemy: [],
|
||||||
mapAvatar: {},
|
mapAvatar: {},
|
||||||
mapEnemy: {},
|
mapEnemy: {},
|
||||||
setListAvatar: (list: CharacterBasic[]) => set({ listAvatar: list }),
|
setListAvatar: (list: CharacterBasic[]) => set({ listAvatar: list }),
|
||||||
setMapAvatar: (data: Record<string, CharacterBasic>) => set({ mapAvatar: data }),
|
setMapAvatar: (data: Record<string, CharacterBasic>) => set({ mapAvatar: data }),
|
||||||
setListEnemy: (list: MonsterBasic[]) => set({ listEnemy: list }),
|
setListEnemy: (list: MonsterBasic[]) => set({ listEnemy: list }),
|
||||||
setMapEnemy: (data: Record<string, MonsterBasic>) => set({ mapEnemy: data })
|
setMapEnemy: (data: Record<string, MonsterBasic>) => set({ mapEnemy: data })
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useAvatarDataStore;
|
export default useAvatarDataStore;
|
||||||
+368
-368
@@ -1,369 +1,369 @@
|
|||||||
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 } from '@/types';
|
||||||
import { InitializeEnemyType } from '@/types/enemy';
|
import { InitializeEnemyType } from '@/types/enemy';
|
||||||
import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
|
import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
|
||||||
interface BattleDataState {
|
interface BattleDataState {
|
||||||
lineup: AvatarBattleInfo[];
|
lineup: AvatarBattleInfo[];
|
||||||
turnHistory: TurnBattleInfo[]
|
turnHistory: TurnBattleInfo[]
|
||||||
skillHistory: SkillBattleInfo[]
|
skillHistory: SkillBattleInfo[]
|
||||||
dataAvatar: AvatarAnalysisJson[]
|
dataAvatar: AvatarAnalysisJson[]
|
||||||
totalAV: number;
|
totalAV: number;
|
||||||
totalDamage: number;
|
totalDamage: number;
|
||||||
damagePerAV: number;
|
damagePerAV: number;
|
||||||
cycleIndex: number;
|
cycleIndex: number;
|
||||||
waveIndex: number;
|
waveIndex: number;
|
||||||
maxWave: number;
|
maxWave: number;
|
||||||
maxCycle: number;
|
maxCycle: number;
|
||||||
version?: string;
|
version?: string;
|
||||||
avatarDetail?: Record<number, AvatarInfo>;
|
avatarDetail?: Record<number, AvatarInfo>;
|
||||||
enemyDetail?: Record<number, EnemyInfo>;
|
enemyDetail?: Record<number, EnemyInfo>;
|
||||||
|
|
||||||
onConnectedService: (data: VersionType) => void
|
onConnectedService: (data: VersionType) => void
|
||||||
onBattleBeginService: (data: BattleBeginType) => void;
|
onBattleBeginService: (data: BattleBeginType) => void;
|
||||||
onSetBattleLineupService: (data: SetBattleLineupType) => void;
|
onSetBattleLineupService: (data: SetBattleLineupType) => void;
|
||||||
onDamageService: (data: DamageType) => void;
|
onDamageService: (data: DamageType) => void;
|
||||||
onTurnBeginService: (data: TurnBeginType) => void;
|
onTurnBeginService: (data: TurnBeginType) => void;
|
||||||
onTurnEndService: (data: TurnEndType) => void;
|
onTurnEndService: (data: TurnEndType) => void;
|
||||||
onEntityDefeatedService: (data: EntityDefeatedType) => void;
|
onEntityDefeatedService: (data: EntityDefeatedType) => void;
|
||||||
onUseSkillService: (data: UseSkillType) => void;
|
onUseSkillService: (data: UseSkillType) => void;
|
||||||
onUpdateWaveService: (data: UpdateWaveType) => void;
|
onUpdateWaveService: (data: UpdateWaveType) => void;
|
||||||
onUpdateCycleService: (data: UpdateCycleType) => void;
|
onUpdateCycleService: (data: UpdateCycleType) => void;
|
||||||
onStatChange: (data: StatChangeType) => void;
|
onStatChange: (data: StatChangeType) => void;
|
||||||
onUpdateTeamFormation: (data: UpdateTeamFormationType) => void;
|
onUpdateTeamFormation: (data: UpdateTeamFormationType) => void;
|
||||||
onInitializeEnemyService: (data: InitializeEnemyType) => void;
|
onInitializeEnemyService: (data: InitializeEnemyType) => void;
|
||||||
onBattleEndService: (data: BattleEndType) => void;
|
onBattleEndService: (data: BattleEndType) => void;
|
||||||
onCreateBattleService: (data: AvatarAnalysisJson[]) => void;
|
onCreateBattleService: (data: AvatarAnalysisJson[]) => void;
|
||||||
loadBattleDataFromJSON: (data: BattleDataStateJson) => void;
|
loadBattleDataFromJSON: (data: BattleDataStateJson) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useBattleDataStore = create<BattleDataState>((set, get) => ({
|
const useBattleDataStore = create<BattleDataState>((set, get) => ({
|
||||||
lineup: [],
|
lineup: [],
|
||||||
turnHistory: [],
|
turnHistory: [],
|
||||||
skillHistory: [],
|
skillHistory: [],
|
||||||
dataAvatar: [],
|
dataAvatar: [],
|
||||||
totalAV: 0,
|
totalAV: 0,
|
||||||
totalDamage: 0,
|
totalDamage: 0,
|
||||||
damagePerAV: 0,
|
damagePerAV: 0,
|
||||||
cycleIndex: 0,
|
cycleIndex: 0,
|
||||||
waveIndex: 1,
|
waveIndex: 1,
|
||||||
maxWave: Infinity,
|
maxWave: Infinity,
|
||||||
maxCycle: Infinity,
|
maxCycle: Infinity,
|
||||||
version: undefined,
|
version: undefined,
|
||||||
avatarDetail: undefined,
|
avatarDetail: undefined,
|
||||||
enemyDetail: undefined,
|
enemyDetail: undefined,
|
||||||
loadBattleDataFromJSON: (data: BattleDataStateJson) => {
|
loadBattleDataFromJSON: (data: BattleDataStateJson) => {
|
||||||
const skillHistory = data.skillHistory.map(it => {
|
const skillHistory = data.skillHistory.map(it => {
|
||||||
it.damageDetail = it.damageDetail.map(it => {
|
it.damageDetail = it.damageDetail.map(it => {
|
||||||
return {
|
return {
|
||||||
...it,
|
...it,
|
||||||
damage_type: ParseAttackType(it.damage_type)
|
damage_type: ParseAttackType(it.damage_type)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return it
|
return it
|
||||||
})
|
})
|
||||||
set({
|
set({
|
||||||
lineup: data.lineup,
|
lineup: data.lineup,
|
||||||
turnHistory: data.turnHistory,
|
turnHistory: data.turnHistory,
|
||||||
skillHistory: skillHistory,
|
skillHistory: skillHistory,
|
||||||
dataAvatar: data.dataAvatar,
|
dataAvatar: data.dataAvatar,
|
||||||
totalAV: data.totalAV,
|
totalAV: data.totalAV,
|
||||||
totalDamage: data.totalDamage,
|
totalDamage: data.totalDamage,
|
||||||
damagePerAV: data.damagePerAV,
|
damagePerAV: data.damagePerAV,
|
||||||
cycleIndex: data.cycleIndex,
|
cycleIndex: data.cycleIndex,
|
||||||
waveIndex: data.waveIndex,
|
waveIndex: data.waveIndex,
|
||||||
maxWave: data.maxWave,
|
maxWave: data.maxWave,
|
||||||
maxCycle: data.maxCycle,
|
maxCycle: data.maxCycle,
|
||||||
version: data.version,
|
version: data.version,
|
||||||
avatarDetail: data.avatarDetail,
|
avatarDetail: data.avatarDetail,
|
||||||
enemyDetail: data.enemyDetail
|
enemyDetail: data.enemyDetail
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onConnectedService: (data: VersionType) => {
|
onConnectedService: (data: VersionType) => {
|
||||||
set({
|
set({
|
||||||
version: data.version
|
version: data.version
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onCreateBattleService: (data: AvatarAnalysisJson[]) => {
|
onCreateBattleService: (data: AvatarAnalysisJson[]) => {
|
||||||
set({
|
set({
|
||||||
dataAvatar: data
|
dataAvatar: data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onBattleBeginService: (data: BattleBeginType) => {
|
onBattleBeginService: (data: BattleBeginType) => {
|
||||||
set({
|
set({
|
||||||
maxWave: data.max_waves,
|
maxWave: data.max_waves,
|
||||||
maxCycle: data.max_cycles
|
maxCycle: data.max_cycles
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onSetBattleLineupService: (data: SetBattleLineupType) => {
|
onSetBattleLineupService: (data: SetBattleLineupType) => {
|
||||||
const lineups: AvatarBattleInfo[] = []
|
const lineups: AvatarBattleInfo[] = []
|
||||||
for (const avatar of data.avatars) {
|
for (const avatar of data.avatars) {
|
||||||
lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo)
|
lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo)
|
||||||
}
|
}
|
||||||
set(() => ({
|
set(() => ({
|
||||||
lineup: lineups,
|
lineup: lineups,
|
||||||
turnHistory: [{
|
turnHistory: [{
|
||||||
avatarId: -1,
|
avatarId: -1,
|
||||||
actionValue: 0,
|
actionValue: 0,
|
||||||
waveIndex: 1,
|
waveIndex: 1,
|
||||||
cycleIndex: 0,
|
cycleIndex: 0,
|
||||||
} as TurnBattleInfo],
|
} as TurnBattleInfo],
|
||||||
skillHistory: [],
|
skillHistory: [],
|
||||||
totalAV: 0,
|
totalAV: 0,
|
||||||
totalDamage: 0,
|
totalDamage: 0,
|
||||||
damagePerAV: 0,
|
damagePerAV: 0,
|
||||||
cycleIndex: 0,
|
cycleIndex: 0,
|
||||||
maxWave: Infinity,
|
maxWave: Infinity,
|
||||||
maxCycle: Infinity,
|
maxCycle: Infinity,
|
||||||
waveIndex: 1,
|
waveIndex: 1,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onDamageService: (data: DamageType) => {
|
onDamageService: (data: DamageType) => {
|
||||||
const skillHistory = get().skillHistory
|
const skillHistory = get().skillHistory
|
||||||
|
|
||||||
const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.uid)
|
const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.uid)
|
||||||
if (skillIdx === -1) {
|
if (skillIdx === -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newTh = [...skillHistory]
|
const newTh = [...skillHistory]
|
||||||
newTh[skillIdx].damageDetail.push({
|
newTh[skillIdx].damageDetail.push({
|
||||||
damage: data.damage,
|
damage: data.damage,
|
||||||
overkill_damage: data.overkill_damage,
|
overkill_damage: data.overkill_damage,
|
||||||
damage_type: ParseAttackType(data.damage_type ? data.damage_type : data.type),
|
damage_type: ParseAttackType(data.damage_type ? data.damage_type : data.type),
|
||||||
} as DamageDetailType)
|
} as DamageDetailType)
|
||||||
newTh[skillIdx].totalDamage += data.damage
|
newTh[skillIdx].totalDamage += data.damage
|
||||||
set({
|
set({
|
||||||
skillHistory: newTh,
|
skillHistory: newTh,
|
||||||
totalDamage: get().totalDamage + data.damage,
|
totalDamage: get().totalDamage + data.damage,
|
||||||
damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV)
|
damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onTurnBeginService: (data: TurnBeginType) => {
|
onTurnBeginService: (data: TurnBeginType) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
totalAV: data.action_value,
|
totalAV: data.action_value,
|
||||||
damagePerAV: state.totalDamage / (data.action_value === 0 ? 1 : data.action_value),
|
damagePerAV: state.totalDamage / (data.action_value === 0 ? 1 : data.action_value),
|
||||||
turnHistory: [...state.turnHistory, {
|
turnHistory: [...state.turnHistory, {
|
||||||
avatarId: data?.turn_owner?.uid,
|
avatarId: data?.turn_owner?.uid,
|
||||||
actionValue: data.action_value,
|
actionValue: data.action_value,
|
||||||
waveIndex: state.waveIndex,
|
waveIndex: state.waveIndex,
|
||||||
cycleIndex: state.cycleIndex
|
cycleIndex: state.cycleIndex
|
||||||
} as TurnBattleInfo]
|
} as TurnBattleInfo]
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
onTurnEndService: (data: TurnEndType) => {
|
onTurnEndService: (data: TurnEndType) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
totalDamage: state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage,
|
totalDamage: state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage,
|
||||||
currentAV: data.turn_info.action_value,
|
currentAV: data.turn_info.action_value,
|
||||||
damagePerAV: (state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage)
|
damagePerAV: (state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage)
|
||||||
/ (data.turn_info.action_value === 0 ? 1 : data.turn_info.action_value)
|
/ (data.turn_info.action_value === 0 ? 1 : data.turn_info.action_value)
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onEntityDefeatedService: (data: EntityDefeatedType) => {
|
onEntityDefeatedService: (data: EntityDefeatedType) => {
|
||||||
let avatarDetail = get().avatarDetail
|
let avatarDetail = get().avatarDetail
|
||||||
let enemyDetail = get().enemyDetail
|
let enemyDetail = get().enemyDetail
|
||||||
if (!enemyDetail) {
|
if (!enemyDetail) {
|
||||||
enemyDetail = {} as Record<number, EnemyInfo>
|
enemyDetail = {} as Record<number, EnemyInfo>
|
||||||
}
|
}
|
||||||
if (!avatarDetail) {
|
if (!avatarDetail) {
|
||||||
avatarDetail = {} as Record<number, AvatarInfo>
|
avatarDetail = {} as Record<number, AvatarInfo>
|
||||||
}
|
}
|
||||||
if (data.killer.team === "Player" && enemyDetail[data.entity_defeated.uid]) {
|
if (data.killer.team === "Player" && enemyDetail[data.entity_defeated.uid]) {
|
||||||
enemyDetail[data.entity_defeated.uid].isDie = true
|
enemyDetail[data.entity_defeated.uid].isDie = true
|
||||||
enemyDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
|
enemyDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
|
||||||
} else if (data.killer.team === "Enemy" && avatarDetail[data.entity_defeated.uid]) {
|
} else if (data.killer.team === "Enemy" && avatarDetail[data.entity_defeated.uid]) {
|
||||||
avatarDetail[data.entity_defeated.uid].isDie = true
|
avatarDetail[data.entity_defeated.uid].isDie = true
|
||||||
avatarDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
|
avatarDetail[data.entity_defeated.uid].killer_uid = data.killer.uid
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
avatarDetail: avatarDetail,
|
avatarDetail: avatarDetail,
|
||||||
enemyDetail: enemyDetail
|
enemyDetail: enemyDetail
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onUseSkillService: (data: UseSkillType) => {
|
onUseSkillService: (data: UseSkillType) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
skillHistory: [...state.skillHistory, {
|
skillHistory: [...state.skillHistory, {
|
||||||
avatarId: data.avatar.uid,
|
avatarId: data.avatar.uid,
|
||||||
damageDetail: [],
|
damageDetail: [],
|
||||||
totalDamage: 0,
|
totalDamage: 0,
|
||||||
skillType: ParseAttackType(data.skill.type),
|
skillType: ParseAttackType(data.skill.type),
|
||||||
skillName: data.skill.name,
|
skillName: data.skill.name,
|
||||||
turnBattleId: state.turnHistory.length - 1
|
turnBattleId: state.turnHistory.length - 1
|
||||||
} as SkillBattleInfo]
|
} as SkillBattleInfo]
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdateWaveService: (data: UpdateWaveType) => {
|
onUpdateWaveService: (data: UpdateWaveType) => {
|
||||||
set({
|
set({
|
||||||
waveIndex: data.wave
|
waveIndex: data.wave
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdateCycleService: (data: UpdateCycleType) => {
|
onUpdateCycleService: (data: UpdateCycleType) => {
|
||||||
set({
|
set({
|
||||||
cycleIndex: data.cycle
|
cycleIndex: data.cycle
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onStatChange: (data: StatChangeType) => {
|
onStatChange: (data: StatChangeType) => {
|
||||||
let avatarDetail = get().avatarDetail
|
let avatarDetail = get().avatarDetail
|
||||||
let enemyDetail = get().enemyDetail
|
let enemyDetail = get().enemyDetail
|
||||||
if (!enemyDetail) {
|
if (!enemyDetail) {
|
||||||
enemyDetail = {} as Record<number, EnemyInfo>
|
enemyDetail = {} as Record<number, EnemyInfo>
|
||||||
}
|
}
|
||||||
if (!avatarDetail) {
|
if (!avatarDetail) {
|
||||||
avatarDetail = {} as Record<number, AvatarInfo>
|
avatarDetail = {} as Record<number, AvatarInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
let key: string;
|
let key: string;
|
||||||
let value: number;
|
let value: number;
|
||||||
if (data.property) {
|
if (data.property) {
|
||||||
key = data.property.type;
|
key = data.property.type;
|
||||||
value = data.property.value;
|
value = data.property.value;
|
||||||
} else if (data.stat) {
|
} else if (data.stat) {
|
||||||
if (
|
if (
|
||||||
data.stat &&
|
data.stat &&
|
||||||
typeof data.stat === 'object' &&
|
typeof data.stat === 'object' &&
|
||||||
'type' in data.stat &&
|
'type' in data.stat &&
|
||||||
'value' in data.stat &&
|
'value' in data.stat &&
|
||||||
typeof data.stat.type === 'string'
|
typeof data.stat.type === 'string'
|
||||||
) {
|
) {
|
||||||
key = data.stat.type;
|
key = data.stat.type;
|
||||||
value = Number(data.stat.value);
|
value = Number(data.stat.value);
|
||||||
} else {
|
} else {
|
||||||
const entries = Object.entries(data.stat);
|
const entries = Object.entries(data.stat);
|
||||||
if (entries.length === 0) return;
|
if (entries.length === 0) return;
|
||||||
[key, value] = entries[0] as [string, number];
|
[key, value] = entries[0] as [string, number];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "CurrentHP") key = "HP";
|
if (key === "CurrentHP") key = "HP";
|
||||||
|
|
||||||
if (data.entity.team === "Player") {
|
if (data.entity.team === "Player") {
|
||||||
const uid = data.entity.uid;
|
const uid = data.entity.uid;
|
||||||
|
|
||||||
if (!avatarDetail[uid]) {
|
if (!avatarDetail[uid]) {
|
||||||
avatarDetail[uid] = {
|
avatarDetail[uid] = {
|
||||||
id: uid,
|
id: uid,
|
||||||
isDie: false,
|
isDie: false,
|
||||||
killer_uid: -1,
|
killer_uid: -1,
|
||||||
stats: {},
|
stats: {},
|
||||||
statsHistory: []
|
statsHistory: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
avatarDetail[uid].stats[key] = value
|
avatarDetail[uid].stats[key] = value
|
||||||
avatarDetail[uid].statsHistory.push({
|
avatarDetail[uid].statsHistory.push({
|
||||||
stats: { [key]: value },
|
stats: { [key]: value },
|
||||||
turnBattleId: get().turnHistory.length - 1
|
turnBattleId: get().turnHistory.length - 1
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const uid = data.entity.uid;
|
const uid = data.entity.uid;
|
||||||
|
|
||||||
if (!enemyDetail[uid]) {
|
if (!enemyDetail[uid]) {
|
||||||
enemyDetail[uid] = {
|
enemyDetail[uid] = {
|
||||||
id: uid,
|
id: uid,
|
||||||
isDie: false,
|
isDie: false,
|
||||||
killer_uid: -1,
|
killer_uid: -1,
|
||||||
name: "",
|
name: "",
|
||||||
positionIndex: Object.keys(get().enemyDetail || {}).length,
|
positionIndex: Object.keys(get().enemyDetail || {}).length,
|
||||||
maxHP: 0,
|
maxHP: 0,
|
||||||
waveIndex: 0,
|
waveIndex: 0,
|
||||||
level: 0,
|
level: 0,
|
||||||
stats: {},
|
stats: {},
|
||||||
statsHistory: []
|
statsHistory: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
enemyDetail[uid].stats[key] = value
|
enemyDetail[uid].stats[key] = value
|
||||||
enemyDetail[uid].statsHistory.push({
|
enemyDetail[uid].statsHistory.push({
|
||||||
stats: { [key]: value },
|
stats: { [key]: value },
|
||||||
turnBattleId: get().turnHistory.length - 1
|
turnBattleId: get().turnHistory.length - 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
avatarDetail: avatarDetail,
|
avatarDetail: avatarDetail,
|
||||||
enemyDetail: enemyDetail
|
enemyDetail: enemyDetail
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdateTeamFormation: (data: UpdateTeamFormationType) => {
|
onUpdateTeamFormation: (data: UpdateTeamFormationType) => {
|
||||||
let avatarDetail = get().avatarDetail
|
let avatarDetail = get().avatarDetail
|
||||||
let enemyDetail = get().enemyDetail
|
let enemyDetail = get().enemyDetail
|
||||||
if (!enemyDetail) {
|
if (!enemyDetail) {
|
||||||
enemyDetail = {} as Record<number, EnemyInfo>
|
enemyDetail = {} as Record<number, EnemyInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!avatarDetail) {
|
if (!avatarDetail) {
|
||||||
avatarDetail = {} as Record<number, AvatarInfo>
|
avatarDetail = {} as Record<number, AvatarInfo>
|
||||||
}
|
}
|
||||||
if (data.team === Team.Enemy) {
|
if (data.team === Team.Enemy) {
|
||||||
for (let i = 0; i < data.entities.length; i++) {
|
for (let i = 0; i < data.entities.length; i++) {
|
||||||
const entity = data.entities[i];
|
const entity = data.entities[i];
|
||||||
if (entity.team === Team.Enemy && enemyDetail?.[entity.uid]) {
|
if (entity.team === Team.Enemy && enemyDetail?.[entity.uid]) {
|
||||||
enemyDetail[entity.uid].positionIndex = i
|
enemyDetail[entity.uid].positionIndex = i
|
||||||
enemyDetail[entity.uid].waveIndex = get().waveIndex
|
enemyDetail[entity.uid].waveIndex = get().waveIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
set({
|
set({
|
||||||
avatarDetail: avatarDetail,
|
avatarDetail: avatarDetail,
|
||||||
enemyDetail: enemyDetail
|
enemyDetail: enemyDetail
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onInitializeEnemyService: (data: InitializeEnemyType) => {
|
onInitializeEnemyService: (data: InitializeEnemyType) => {
|
||||||
const enemyDetail = get().enemyDetail
|
const enemyDetail = get().enemyDetail
|
||||||
if (!enemyDetail) {
|
if (!enemyDetail) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let maxHP = 0;
|
let maxHP = 0;
|
||||||
let level = 0;
|
let level = 0;
|
||||||
if ('properties' in data.enemy.base_stats) {
|
if ('properties' in data.enemy.base_stats) {
|
||||||
maxHP = data.enemy.base_stats.properties["MaxHP"] || 0;
|
maxHP = data.enemy.base_stats.properties["MaxHP"] || 0;
|
||||||
level = data.enemy.base_stats.properties["Level"] || 0;
|
level = data.enemy.base_stats.properties["Level"] || 0;
|
||||||
} else {
|
} else {
|
||||||
maxHP = data.enemy.base_stats.hp || data.enemy.base_stats.CurrentHP || 0;
|
maxHP = data.enemy.base_stats.hp || data.enemy.base_stats.CurrentHP || 0;
|
||||||
level = data.enemy.base_stats.level;
|
level = data.enemy.base_stats.level;
|
||||||
}
|
}
|
||||||
|
|
||||||
enemyDetail[data.enemy.uid] = {
|
enemyDetail[data.enemy.uid] = {
|
||||||
id: data.enemy.id,
|
id: data.enemy.id,
|
||||||
isDie: false,
|
isDie: false,
|
||||||
killer_uid: -1,
|
killer_uid: -1,
|
||||||
positionIndex: enemyDetail?.[data.enemy.uid]?.positionIndex || 0,
|
positionIndex: enemyDetail?.[data.enemy.uid]?.positionIndex || 0,
|
||||||
waveIndex: get().waveIndex,
|
waveIndex: get().waveIndex,
|
||||||
name: data.enemy.name,
|
name: data.enemy.name,
|
||||||
maxHP: maxHP,
|
maxHP: maxHP,
|
||||||
level: level,
|
level: level,
|
||||||
stats: {},
|
stats: {},
|
||||||
statsHistory: []
|
statsHistory: []
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
enemyDetail: enemyDetail
|
enemyDetail: enemyDetail
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onBattleEndService: (data: BattleEndType) => {
|
onBattleEndService: (data: BattleEndType) => {
|
||||||
const lineups: AvatarBattleInfo[] = []
|
const lineups: AvatarBattleInfo[] = []
|
||||||
for (const avatar of data.avatars) {
|
for (const avatar of data.avatars) {
|
||||||
lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo)
|
lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo)
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
lineup: lineups,
|
lineup: lineups,
|
||||||
totalDamage: data.total_damage,
|
totalDamage: data.total_damage,
|
||||||
totalAV: data.action_value,
|
totalAV: data.action_value,
|
||||||
damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value)
|
damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useBattleDataStore;
|
export default useBattleDataStore;
|
||||||
+24
-24
@@ -1,25 +1,25 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
|
||||||
interface LocaleState {
|
interface LocaleState {
|
||||||
locale: string;
|
locale: string;
|
||||||
theme: string
|
theme: string
|
||||||
setTheme: (newTheme: string) => void;
|
setTheme: (newTheme: string) => void;
|
||||||
setLocale: (newLocale: string) => void;
|
setLocale: (newLocale: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useLocaleStore = create<LocaleState>()(persist(
|
const useLocaleStore = create<LocaleState>()(persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
locale: "en",
|
locale: "en",
|
||||||
theme: "night",
|
theme: "night",
|
||||||
setTheme: (newTheme: string) => set({ theme: newTheme }),
|
setTheme: (newTheme: string) => set({ theme: newTheme }),
|
||||||
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'locale-storage',
|
name: 'locale-storage',
|
||||||
storage: createJSONStorage(() => localStorage),
|
storage: createJSONStorage(() => localStorage),
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
export default useLocaleStore;
|
export default useLocaleStore;
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
interface SocketState {
|
interface SocketState {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
connectionType: string;
|
connectionType: string;
|
||||||
setHost: (host: string) => void;
|
setHost: (host: string) => void;
|
||||||
setConnectionType: (host: string) => void;
|
setConnectionType: (host: string) => void;
|
||||||
setPort: (port: number) => void;
|
setPort: (port: number) => void;
|
||||||
setStatus: (status: boolean) => void;
|
setStatus: (status: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useSocketStore = create<SocketState>()(
|
const useSocketStore = create<SocketState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
host: "http://localhost",
|
host: "http://localhost",
|
||||||
port: 3443,
|
port: 3443,
|
||||||
status: false,
|
status: false,
|
||||||
connectionType: "Native",
|
connectionType: "Native",
|
||||||
setHost: (host: string) => set({ host }),
|
setHost: (host: string) => set({ host }),
|
||||||
setConnectionType: (connectionType: string) => set({ connectionType }),
|
setConnectionType: (connectionType: string) => set({ connectionType }),
|
||||||
setPort: (port: number) => set({ port }),
|
setPort: (port: number) => set({ port }),
|
||||||
setStatus: (status: boolean) => set({ status }),
|
setStatus: (status: boolean) => set({ status }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'socket-storage',
|
name: 'socket-storage',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export default useSocketStore;
|
export default useSocketStore;
|
||||||
|
|||||||
+101
-101
@@ -1,101 +1,101 @@
|
|||||||
import { EntityType } from "./entity";
|
import { EntityType } from "./entity";
|
||||||
|
|
||||||
export interface DamageType {
|
export interface DamageType {
|
||||||
attacker: EntityType;
|
attacker: EntityType;
|
||||||
damage: number;
|
damage: number;
|
||||||
overkill_damage?: number;
|
overkill_damage?: number;
|
||||||
damage_type?: AttackType | string;
|
damage_type?: AttackType | string;
|
||||||
type?: AttackType | string;
|
type?: AttackType | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DamageDetailType {
|
export interface DamageDetailType {
|
||||||
damage: number;
|
damage: number;
|
||||||
overkill_damage?: number;
|
overkill_damage?: number;
|
||||||
damage_type?: AttackType;
|
damage_type?: AttackType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export enum AttackType {
|
export enum AttackType {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
BPSkill = 2,
|
BPSkill = 2,
|
||||||
Ultra = 3,
|
Ultra = 3,
|
||||||
QTE = 4,
|
QTE = 4,
|
||||||
DOT = 5,
|
DOT = 5,
|
||||||
Pursued = 6,
|
Pursued = 6,
|
||||||
Maze = 7,
|
Maze = 7,
|
||||||
MazeNormal = 8,
|
MazeNormal = 8,
|
||||||
Insert = 9,
|
Insert = 9,
|
||||||
ElementDamage = 10,
|
ElementDamage = 10,
|
||||||
Level = 11,
|
Level = 11,
|
||||||
Servant = 12,
|
Servant = 12,
|
||||||
TrueDamage = 13,
|
TrueDamage = 13,
|
||||||
ElationDamage = 14
|
ElationDamage = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const attackTypeMap: Record<string, AttackType> = {
|
const attackTypeMap: Record<string, AttackType> = {
|
||||||
Talent: AttackType.Unknown,
|
Talent: AttackType.Unknown,
|
||||||
Basic: AttackType.Normal,
|
Basic: AttackType.Normal,
|
||||||
Skill: AttackType.BPSkill,
|
Skill: AttackType.BPSkill,
|
||||||
Ultimate: AttackType.Ultra,
|
Ultimate: AttackType.Ultra,
|
||||||
QTE: AttackType.QTE,
|
QTE: AttackType.QTE,
|
||||||
DOT: AttackType.DOT,
|
DOT: AttackType.DOT,
|
||||||
DoT: AttackType.DOT,
|
DoT: AttackType.DOT,
|
||||||
Pursued: AttackType.Pursued,
|
Pursued: AttackType.Pursued,
|
||||||
Additional: AttackType.Pursued,
|
Additional: AttackType.Pursued,
|
||||||
Technique: AttackType.Maze,
|
Technique: AttackType.Maze,
|
||||||
MazeNormal: AttackType.MazeNormal,
|
MazeNormal: AttackType.MazeNormal,
|
||||||
"Follow-up": AttackType.Insert,
|
"Follow-up": AttackType.Insert,
|
||||||
"Follow-Up": AttackType.Insert,
|
"Follow-Up": AttackType.Insert,
|
||||||
"Elemental Damage": AttackType.ElementDamage,
|
"Elemental Damage": AttackType.ElementDamage,
|
||||||
Break: AttackType.ElementDamage,
|
Break: AttackType.ElementDamage,
|
||||||
Level: AttackType.Level,
|
Level: AttackType.Level,
|
||||||
Servant: AttackType.Servant,
|
Servant: AttackType.Servant,
|
||||||
"True Damage": AttackType.TrueDamage,
|
"True Damage": AttackType.TrueDamage,
|
||||||
True: AttackType.TrueDamage,
|
True: AttackType.TrueDamage,
|
||||||
"Elation Damage": AttackType.ElationDamage,
|
"Elation Damage": AttackType.ElationDamage,
|
||||||
Elation: AttackType.ElationDamage,
|
Elation: AttackType.ElationDamage,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ParseAttackType(type: AttackType | string | undefined): AttackType {
|
export function ParseAttackType(type: AttackType | string | undefined): AttackType {
|
||||||
if (type === undefined || type === null) {
|
if (type === undefined || type === null) {
|
||||||
return AttackType.Unknown;
|
return AttackType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof type === "number") {
|
if (typeof type === "number") {
|
||||||
return type in AttackType ? type : AttackType.Unknown;
|
return type in AttackType ? type : AttackType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = Number(type);
|
const num = Number(type);
|
||||||
if (!isNaN(num)) {
|
if (!isNaN(num)) {
|
||||||
return num in AttackType ? num as AttackType : AttackType.Unknown;
|
return num in AttackType ? num as AttackType : AttackType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attackTypeMap[type] ?? AttackType.Unknown;
|
return attackTypeMap[type] ?? AttackType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attackTypeToString(type: AttackType | undefined): string {
|
export function attackTypeToString(type: AttackType | undefined): string {
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AttackType.Unknown: return "Talent";
|
case AttackType.Unknown: return "Talent";
|
||||||
case AttackType.Normal: return "Basic";
|
case AttackType.Normal: return "Basic";
|
||||||
case AttackType.BPSkill: return "Skill";
|
case AttackType.BPSkill: return "Skill";
|
||||||
case AttackType.Ultra: return "Ultimate";
|
case AttackType.Ultra: return "Ultimate";
|
||||||
case AttackType.QTE: return "QTE";
|
case AttackType.QTE: return "QTE";
|
||||||
case AttackType.DOT: return "DOT";
|
case AttackType.DOT: return "DOT";
|
||||||
case AttackType.Pursued: return "Pursued";
|
case AttackType.Pursued: return "Pursued";
|
||||||
case AttackType.Maze: return "Technique";
|
case AttackType.Maze: return "Technique";
|
||||||
case AttackType.MazeNormal: return "MazeNormal";
|
case AttackType.MazeNormal: return "MazeNormal";
|
||||||
case AttackType.Insert: return "Follow-up";
|
case AttackType.Insert: return "Follow-up";
|
||||||
case AttackType.ElementDamage: return "Elemental Damage";
|
case AttackType.ElementDamage: return "Elemental Damage";
|
||||||
case AttackType.Level: return "Level";
|
case AttackType.Level: return "Level";
|
||||||
case AttackType.Servant: return "Servant";
|
case AttackType.Servant: return "Servant";
|
||||||
case AttackType.TrueDamage: return "True Damage";
|
case AttackType.TrueDamage: return "True Damage";
|
||||||
case AttackType.ElationDamage: return "Elation Damage";
|
case AttackType.ElationDamage: return "Elation Damage";
|
||||||
default: return "Unknown";
|
default: return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
export interface CharacterBasic {
|
export interface CharacterBasic {
|
||||||
id: string;
|
id: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
rank: string;
|
rank: string;
|
||||||
baseType: string;
|
baseType: string;
|
||||||
damageType: string;
|
damageType: string;
|
||||||
lang: Record<string, string>;
|
lang: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -1,18 +1,18 @@
|
|||||||
import { AvatarType } from "./lineup";
|
import { AvatarType } from "./lineup";
|
||||||
import { TurnInfoType } from "./turn";
|
import { TurnInfoType } from "./turn";
|
||||||
|
|
||||||
export interface BattleEndType {
|
export interface BattleEndType {
|
||||||
avatars: AvatarType[];
|
avatars: AvatarType[];
|
||||||
turn_history: TurnInfoType[];
|
turn_history: TurnInfoType[];
|
||||||
av_history: TurnInfoType[];
|
av_history: TurnInfoType[];
|
||||||
turn_count: number;
|
turn_count: number;
|
||||||
total_damage: number;
|
total_damage: number;
|
||||||
action_value: number;
|
action_value: number;
|
||||||
stage_id: number;
|
stage_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BattleBeginType {
|
export interface BattleBeginType {
|
||||||
max_waves: number
|
max_waves: number
|
||||||
max_cycles: number
|
max_cycles: number
|
||||||
stage_id: number
|
stage_id: number
|
||||||
}
|
}
|
||||||
+26
-26
@@ -1,26 +1,26 @@
|
|||||||
import { StatsType } from "./stat";
|
import { StatsType } from "./stat";
|
||||||
|
|
||||||
export interface BattleStatsType {
|
export interface BattleStatsType {
|
||||||
properties: Record<string, number>;
|
properties: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnemyType {
|
export interface EnemyType {
|
||||||
id: number;
|
id: number;
|
||||||
uid: number;
|
uid: number;
|
||||||
name: string;
|
name: string;
|
||||||
base_stats: BattleStatsType | StatsType;
|
base_stats: BattleStatsType | StatsType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitializeEnemyType {
|
export interface InitializeEnemyType {
|
||||||
enemy: EnemyType
|
enemy: EnemyType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonsterBasic {
|
export interface MonsterBasic {
|
||||||
id: string;
|
id: string;
|
||||||
rank: string;
|
rank: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
image: string;
|
image: string;
|
||||||
weak: string[];
|
weak: string[];
|
||||||
desc: Record<string, string>;
|
desc: Record<string, string>;
|
||||||
lang: Record<string, string>;
|
lang: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,11 +1,11 @@
|
|||||||
import { Team } from "./team";
|
import { Team } from "./team";
|
||||||
|
|
||||||
export interface EntityType {
|
export interface EntityType {
|
||||||
uid: number;
|
uid: number;
|
||||||
team: Team;
|
team: Team;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityDefeatedType {
|
export interface EntityDefeatedType {
|
||||||
killer: EntityType,
|
killer: EntityType,
|
||||||
entity_defeated: EntityType
|
entity_defeated: EntityType
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
export interface ErrorType {
|
export interface ErrorType {
|
||||||
msg: string
|
msg: string
|
||||||
}
|
}
|
||||||
+13
-13
@@ -1,13 +1,13 @@
|
|||||||
export * from "./attack"
|
export * from "./attack"
|
||||||
export * from "./avatar"
|
export * from "./avatar"
|
||||||
export * from "./battle"
|
export * from "./battle"
|
||||||
export * from "./lineup"
|
export * from "./lineup"
|
||||||
export * from "./skill"
|
export * from "./skill"
|
||||||
export * from "./turn"
|
export * from "./turn"
|
||||||
export * from "./waveAndCycle"
|
export * from "./waveAndCycle"
|
||||||
export * from "./srtools"
|
export * from "./srtools"
|
||||||
export * from "./version"
|
export * from "./version"
|
||||||
export * from "./entity"
|
export * from "./entity"
|
||||||
export * from "./stat"
|
export * from "./stat"
|
||||||
export * from "./enemy"
|
export * from "./enemy"
|
||||||
export * from "./team"
|
export * from "./team"
|
||||||
|
|||||||
+16
-16
@@ -1,17 +1,17 @@
|
|||||||
import { EntityType } from "./entity";
|
import { EntityType } from "./entity";
|
||||||
import { Team } from "./team";
|
import { Team } from "./team";
|
||||||
|
|
||||||
export interface AvatarType{
|
export interface AvatarType{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetBattleLineupType {
|
export interface SetBattleLineupType {
|
||||||
avatars: AvatarType[];
|
avatars: AvatarType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface UpdateTeamFormationType {
|
export interface UpdateTeamFormationType {
|
||||||
entities: EntityType[],
|
entities: EntityType[],
|
||||||
team: Team
|
team: Team
|
||||||
}
|
}
|
||||||
+68
-68
@@ -1,68 +1,68 @@
|
|||||||
import {AttackType, DamageDetailType} from "./attack";
|
import {AttackType, DamageDetailType} from "./attack";
|
||||||
import { AvatarAnalysisJson } from "./srtools";
|
import { AvatarAnalysisJson } from "./srtools";
|
||||||
import { StatType } from "./stat";
|
import { StatType } from "./stat";
|
||||||
|
|
||||||
|
|
||||||
export interface AvatarBattleInfo {
|
export interface AvatarBattleInfo {
|
||||||
avatarId: number;
|
avatarId: number;
|
||||||
isDie?: boolean;
|
isDie?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SkillBattleInfo {
|
export interface SkillBattleInfo {
|
||||||
avatarId: number;
|
avatarId: number;
|
||||||
damageDetail: DamageDetailType[];
|
damageDetail: DamageDetailType[];
|
||||||
totalDamage: number;
|
totalDamage: number;
|
||||||
skillType: AttackType;
|
skillType: AttackType;
|
||||||
skillName: string;
|
skillName: string;
|
||||||
turnBattleId: number;
|
turnBattleId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnBattleInfo {
|
export interface TurnBattleInfo {
|
||||||
avatarId: number;
|
avatarId: number;
|
||||||
actionValue: number;
|
actionValue: number;
|
||||||
waveIndex: number;
|
waveIndex: number;
|
||||||
cycleIndex: number;
|
cycleIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BattleDataStateJson {
|
export interface BattleDataStateJson {
|
||||||
lineup: AvatarBattleInfo[];
|
lineup: AvatarBattleInfo[];
|
||||||
turnHistory: TurnBattleInfo[]
|
turnHistory: TurnBattleInfo[]
|
||||||
skillHistory: SkillBattleInfo[]
|
skillHistory: SkillBattleInfo[]
|
||||||
dataAvatar: AvatarAnalysisJson[]
|
dataAvatar: AvatarAnalysisJson[]
|
||||||
totalAV: number;
|
totalAV: number;
|
||||||
totalDamage: number;
|
totalDamage: number;
|
||||||
damagePerAV: number;
|
damagePerAV: number;
|
||||||
maxWave: number;
|
maxWave: number;
|
||||||
cycleIndex: number,
|
cycleIndex: number,
|
||||||
waveIndex: number,
|
waveIndex: number,
|
||||||
maxCycle: number,
|
maxCycle: number,
|
||||||
version?: string,
|
version?: string,
|
||||||
avatarDetail?: Record<number, AvatarInfo>;
|
avatarDetail?: Record<number, AvatarInfo>;
|
||||||
enemyDetail?: Record<number, EnemyInfo>;
|
enemyDetail?: Record<number, EnemyInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatsHistoryType {
|
export interface StatsHistoryType {
|
||||||
stats: StatType
|
stats: StatType
|
||||||
turnBattleId: number;
|
turnBattleId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnemyInfo {
|
export interface EnemyInfo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
maxHP: number;
|
maxHP: number;
|
||||||
level: number;
|
level: number;
|
||||||
isDie: boolean;
|
isDie: boolean;
|
||||||
positionIndex: number;
|
positionIndex: number;
|
||||||
waveIndex: number;
|
waveIndex: number;
|
||||||
killer_uid: number;
|
killer_uid: number;
|
||||||
stats: Record<string, number>;
|
stats: Record<string, number>;
|
||||||
statsHistory: StatsHistoryType[];
|
statsHistory: StatsHistoryType[];
|
||||||
}
|
}
|
||||||
export interface AvatarInfo {
|
export interface AvatarInfo {
|
||||||
id: number;
|
id: number;
|
||||||
isDie: boolean;
|
isDie: boolean;
|
||||||
killer_uid: number;
|
killer_uid: number;
|
||||||
stats: Record<string, number>;
|
stats: Record<string, number>;
|
||||||
statsHistory: StatsHistoryType[];
|
statsHistory: StatsHistoryType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -1,14 +1,14 @@
|
|||||||
|
|
||||||
import { AttackType } from "@/types/attack";
|
import { AttackType } from "@/types/attack";
|
||||||
import { EntityType } from "./entity";
|
import { EntityType } from "./entity";
|
||||||
|
|
||||||
export interface SkillInfo {
|
export interface SkillInfo {
|
||||||
name: string;
|
name: string;
|
||||||
type: AttackType | string;
|
type: AttackType | string;
|
||||||
skill_config_id: number;
|
skill_config_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseSkillType {
|
export interface UseSkillType {
|
||||||
avatar: EntityType;
|
avatar: EntityType;
|
||||||
skill: SkillInfo;
|
skill: SkillInfo;
|
||||||
}
|
}
|
||||||
+42
-42
@@ -1,42 +1,42 @@
|
|||||||
export interface AvatarAnalysisJson {
|
export interface AvatarAnalysisJson {
|
||||||
owner_uid: number;
|
owner_uid: number;
|
||||||
avatar_id: number;
|
avatar_id: number;
|
||||||
data: AvatarData | null;
|
data: AvatarData | null;
|
||||||
level: number;
|
level: number;
|
||||||
promotion: number;
|
promotion: number;
|
||||||
techniques: number[];
|
techniques: number[];
|
||||||
relics: RelicJson[];
|
relics: RelicJson[];
|
||||||
Lightcone: LightconeJson | null;
|
Lightcone: LightconeJson | null;
|
||||||
sp_value: number;
|
sp_value: number;
|
||||||
sp_max: number;
|
sp_max: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvatarData {
|
export interface AvatarData {
|
||||||
rank: number;
|
rank: number;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubAffix {
|
export interface SubAffix {
|
||||||
sub_affix_id: number;
|
sub_affix_id: number;
|
||||||
count: number;
|
count: number;
|
||||||
step: number;
|
step: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelicJson {
|
export interface RelicJson {
|
||||||
level: number;
|
level: number;
|
||||||
relic_id: number;
|
relic_id: number;
|
||||||
relic_set_id: number;
|
relic_set_id: number;
|
||||||
main_affix_id: number;
|
main_affix_id: number;
|
||||||
sub_affixes: SubAffix[];
|
sub_affixes: SubAffix[];
|
||||||
internal_uid: number;
|
internal_uid: number;
|
||||||
equip_avatar: number;
|
equip_avatar: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LightconeJson {
|
export interface LightconeJson {
|
||||||
level: number;
|
level: number;
|
||||||
item_id: number;
|
item_id: number;
|
||||||
equip_avatar: number;
|
equip_avatar: number;
|
||||||
rank: number;
|
rank: number;
|
||||||
promotion: number;
|
promotion: number;
|
||||||
internal_uid: number;
|
internal_uid: number;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
export enum Team {
|
export enum Team {
|
||||||
Player = "Player",
|
Player = "Player",
|
||||||
Enemy = "Enemy"
|
Enemy = "Enemy"
|
||||||
}
|
}
|
||||||
+17
-17
@@ -1,18 +1,18 @@
|
|||||||
import { EntityType } from "./entity";
|
import { EntityType } from "./entity";
|
||||||
|
|
||||||
export interface TurnInfoType {
|
export interface TurnInfoType {
|
||||||
avatars_turn_damage: number[];
|
avatars_turn_damage: number[];
|
||||||
total_damage: number;
|
total_damage: number;
|
||||||
action_value: number,
|
action_value: number,
|
||||||
cycle: number,
|
cycle: number,
|
||||||
wave: number,
|
wave: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnBeginType {
|
export interface TurnBeginType {
|
||||||
action_value: number;
|
action_value: number;
|
||||||
turn_owner?: EntityType | null
|
turn_owner?: EntityType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnEndType {
|
export interface TurnEndType {
|
||||||
turn_info: TurnInfoType
|
turn_info: TurnInfoType
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export interface VersionType {
|
export interface VersionType {
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface UpdateWaveType {
|
export interface UpdateWaveType {
|
||||||
wave: number
|
wave: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateCycleType {
|
export interface UpdateCycleType {
|
||||||
cycle: number
|
cycle: number
|
||||||
}
|
}
|
||||||
+1
-1
@@ -38,4 +38,4 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user