From f9fd97598efea73ceae17f590409cfb6f0260bb6 Mon Sep 17 00:00:00 2001 From: AzenKain Date: Fri, 2 May 2025 16:54:55 +0700 Subject: [PATCH] update wave, cycle, damage type ... --- messages/en.json | 41 +- messages/ja.json | 83 ++- messages/ko.json | 81 ++- messages/vi.json | 41 +- messages/zh.json | 37 +- next.config.ts | 7 +- package-lock.json | 45 ++ package.json | 2 + src/app/globals.css | 23 + src/app/layout.tsx | 2 +- src/app/page.tsx | 124 ++-- src/components/actionbar/index.tsx | 122 ++-- src/components/card/characterCard.tsx | 4 +- src/components/card/showCaseCard.tsx | 665 ++++++++++++++++++ src/components/chart/damageLineForAll.tsx | 40 +- src/components/chart/damageLineForOne.tsx | 31 +- .../chart/damagePerAvatarForAll.tsx | 162 ++++- src/components/chart/damagePerCycleForAll.tsx | 40 +- src/components/chart/damagePerCycleForOne.tsx | 34 +- src/components/chart/damagePercentForAll.tsx | 52 +- src/components/chart/skillBarChart.tsx | 112 +-- src/components/chart/skillPieChart.tsx | 116 +-- src/components/header/index.tsx | 15 +- src/components/lineupbar/index.tsx | 223 +++--- src/helper/exportDataBattle.ts | 51 +- src/helper/getNameChar.ts | 4 +- src/hooks/useCalcAvatarData.ts | 6 +- src/hooks/useDamageLineForOne.ts | 14 +- src/hooks/useDamageLinesForAll.ts | 21 +- src/hooks/useDamagePerCycle.ts | 116 ++- src/hooks/useDamagePercentForDamgeType.ts | 26 + src/hooks/useDamagePercentPerAvatar.ts | 6 +- src/hooks/useDamageTypeForAvatar.ts | 26 + src/hooks/useSkillDamageForAvatar.ts | 24 + src/hooks/useSkillDamageStats.ts | 25 - src/lib/api.ts | 10 +- src/lib/socket.ts | 47 +- src/stores/avatarDataStore.ts | 8 +- src/stores/battleDataStore.ts | 111 ++- src/types/attack.ts | 53 +- src/types/avatar.ts | 4 +- src/types/battle.ts | 17 +- src/types/index.ts | 4 +- src/types/lineup.ts | 4 +- src/types/mics.ts | 24 +- src/types/skill.ts | 7 +- src/types/srtools.ts | 42 ++ src/types/turn.ts | 15 +- src/types/waveAndCycle.ts | 7 + 49 files changed, 2072 insertions(+), 702 deletions(-) create mode 100644 src/components/card/showCaseCard.tsx create mode 100644 src/hooks/useDamagePercentForDamgeType.ts create mode 100644 src/hooks/useDamageTypeForAvatar.ts create mode 100644 src/hooks/useSkillDamageForAvatar.ts delete mode 100644 src/hooks/useSkillDamageStats.ts create mode 100644 src/types/srtools.ts create mode 100644 src/types/waveAndCycle.ts diff --git a/messages/en.json b/messages/en.json index 2c32730..ebabbd3 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4,11 +4,10 @@ "description": "Analytics tool for Veritas" }, "DataAnalysisPage": { - "useSkill": "Use skill", + "useSkill": "Use Ability", "totalDamage": "Total damage", - "damagePerAV": "Damage per AV", + "damagePerAV": "Damage/Action value", "totalAV": "Total action value", - "damagerPerCycle": "Damage Per Cycle", "skillType": "Skill Type", "skillName": "Skill Name", "actionValue": "Action Value", @@ -70,6 +69,40 @@ "physical": "Physical", "quantum": "Quantum", "thunder": "Thunder", - "wind": "Wind" + "wind": "Wind", + "cycle": "Cycle", + "wave": "Wave", + "hp": "Hp", + "atk": "Atk", + "speed": "Speed", + "critRate": "Crit Rate", + "critDmg": "Crit Dmg", + "breakEffect": "Break Effect", + "effectRes": "Effect Res", + "energyRegenerationRate": "Energy Regeneration Rate", + "effectHitRate": "Effect Hit Rate", + "outgoingHealingBoost": "Outgoing Healing Boost", + "fireDmgBoost": "Fire damage boost", + "iceDmgBoost": "Ice damage Boost", + "imaginaryDmgBoost": "Imaginary damage boost", + "physicalDmgBoost": "Physical damage boost", + "quantumDmgBoost": "Quantum damage boost", + "thunderDmgBoost": "Thunder damage boost", + "windDmgBoost": "Wind damage boost", + "pursued": "Additional damage", + "true damage": "True damage", + "follow-up": "Follow-up Damage", + "elemental damage": "Break and Super break damage", + "dot": "Damage over time ", + "damagePerCycle": "Damage per Cycle", + "damagePerCycleAndWave": "Damage per Cycle and Wave", + "damagePerWave": "Damage per Wave", + "lastTurn": "Last turn", + "qte": "QTE Skill", + "mazenormal": "MazeNormal", + "level": "Level", + "relics": "Relics", + "eidolons": "Eidolons", + "lightcones": "Lightcones" } } diff --git a/messages/ja.json b/messages/ja.json index 0db06cf..6dbabd6 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -4,32 +4,31 @@ "description": "Veritasの分析ツール" }, "DataAnalysisPage": { - "useSkill": "スキル使用", + "useSkill": "スキルを使用", "totalDamage": "総ダメージ", - "damagePerAV": "AVあたりのダメージ", - "totalAV": "総AV", - "damagerPerCycle": "サイクルごとのダメージ", + "damagePerAV": "ダメージ/行動値", + "totalAV": "総行動値", "skillType": "スキルタイプ", "skillName": "スキル名", - "actionValue": "アクション値", + "actionValue": "行動値", "character": "キャラクター", "id": "ID", - "path": "パス", - "damageByActionValue": "行動値によるダメージ", + "path": "運命", "rarity": "レアリティ", - "element": "エレメンタル", - "totalTurn": "総ターン", - "technique": "テクニック", - "talent": "タレント", - "basic": "基本攻撃", + "element": "属性", + "totalTurn": "総ターン数", + "technique": "秘技", + "talent": "天賦", + "basic": "通常攻撃", "skill": "スキル", - "ultimate": "アルティメット", - "servant": "サーヴァント", + "ultimate": "必殺技", + "servant": "召喚", "skillDamageBreakdown": "スキルダメージ内訳", "skillUsageDistribution": "スキル使用分布", - "damageOverTime": "時間経過によるダメージ", + "damageOverTime": "継続ダメージ", "damage": "ダメージ", "cumulativeDamage": "累積ダメージ", + "damageByActionValue": "行動値別ダメージ", "characterInformation": "キャラクター情報", "turnDetail": "ターン詳細", "damageDetails": "ダメージ詳細", @@ -37,28 +36,28 @@ "chartInfo": "チャート情報", "actionBar": "アクションタイムライン", "lineupInfo": "編成情報", - "loadData": "バトルデータをロード", - "exportData": "バトルデータをエクスポート", + "loadData": "戦闘データを読み込む", + "exportData": "戦闘データをエクスポート", "connectSetting": "接続設定", "connected": "接続済み", - "unconnected": "接続していない", + "unconnected": "未接続", "socketConnection": "ソケット接続", "connectionType": "接続タイプ", "status": "ステータス", - "connect": "接続", + "connect": "接続する", "checkGameConnect": "ゲーム接続を確認", "other": "その他", "host": "ホスト", "port": "ポート", "hostPlaceHolder": "ホストを入力", "portPlaceHolder": "ポート番号を入力", - "noDamageDetail": "ダメージの詳細は利用できません", + "noDamageDetail": "ダメージ詳細がありません", "noCharactersInLineup": "編成にキャラクターがいません", "noTurns": "まだターンがありません", "type": "タイプ", - "warrior": "破壊", - "knight": "保護", - "mage": "博識", + "warrior": "壊滅", + "knight": "存護", + "mage": "知恵", "priest": "豊穣", "rouge": "巡狩", "shaman": "調和", @@ -66,10 +65,44 @@ "memory": "記憶", "fire": "炎", "ice": "氷", - "imaginary": "想像", + "imaginary": "虚数", "physical": "物理", "quantum": "量子", "thunder": "雷", - "wind": "風" + "wind": "風", + "cycle": "サイクル", + "wave": "ウェーブ", + "hp": "HP", + "atk": "攻撃力", + "speed": "速度", + "critRate": "会心率", + "critDmg": "会心ダメージ", + "breakEffect": "撃破特効", + "effectRes": "効果抵抗", + "energyRegenerationRate": "エネルギー回復効率", + "effectHitRate": "効果命中率", + "outgoingHealingBoost": "治癒量上昇", + "fireDmgBoost": "炎属性ダメージ上昇", + "iceDmgBoost": "氷属性ダメージ上昇", + "imaginaryDmgBoost": "虚数属性ダメージ上昇", + "physicalDmgBoost": "物理属性ダメージ上昇", + "quantumDmgBoost": "量子属性ダメージ上昇", + "thunderDmgBoost": "雷属性ダメージ上昇", + "windDmgBoost": "風属性ダメージ上昇", + "pursued": "追加ダメージ", + "true damage": "貫通ダメージ", + "follow-up": "追撃ダメージ", + "elemental damage": "撃破・超撃破ダメージ", + "dot": "継続ダメージ", + "damagePerCycle": "サイクル毎のダメージ", + "damagePerCycleAndWave": "サイクル・ウェーブ毎のダメージ", + "damagePerWave": "ウェーブ毎のダメージ", + "lastTurn": "最後のターン", + "qte": "QTEスキル", + "mazenormal": "通常迷宮", + "level": "レベル", + "relics": "遺物", + "eidolons": "星魂", + "lightcones": "光円錐" } } diff --git a/messages/ko.json b/messages/ko.json index d3ebeda..173b486 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -5,39 +5,38 @@ }, "DataAnalysisPage": { "useSkill": "스킬 사용", - "totalDamage": "총 피해", - "damagePerAV": "AV당 피해", - "totalAV": "총 AV", - "damagerPerCycle": "주기당 피해", + "totalDamage": "총 피해량", + "damagePerAV": "피해량/행동값", + "totalAV": "총 행동값", "skillType": "스킬 유형", "skillName": "스킬 이름", - "actionValue": "액션 값", + "actionValue": "행동값", "character": "캐릭터", "id": "ID", - "path": "경로", + "path": "운명", "rarity": "희귀도", - "damageByActionValue": "행동값에 따른 피해", - "element": "원소", - "totalTurn": "총 턴", - "technique": "기술", + "element": "속성", + "totalTurn": "총 턴 수", + "technique": "비전", "talent": "재능", "basic": "기본 공격", "skill": "스킬", - "ultimate": "궁극기", - "servant": "서번트", - "skillDamageBreakdown": "스킬 피해 분석", + "ultimate": "필살기", + "servant": "소환", + "skillDamageBreakdown": "스킬 피해 세부사항", "skillUsageDistribution": "스킬 사용 분포", - "damageOverTime": "시간 경과에 따른 피해", + "damageOverTime": "시간당 피해", "damage": "피해", "cumulativeDamage": "누적 피해", + "damageByActionValue": "행동값별 피해", "characterInformation": "캐릭터 정보", - "turnDetail": "턴 상세", - "damageDetails": "피해 상세", + "turnDetail": "턴 상세 정보", + "damageDetails": "피해 세부사항", "cycleCount": "사이클 수", "chartInfo": "차트 정보", - "actionBar": "액션 타임라인", - "lineupInfo": "라인업 정보", - "loadData": "전투 데이터 로드", + "actionBar": "행동 타임라인", + "lineupInfo": "편성 정보", + "loadData": "전투 데이터 불러오기", "exportData": "전투 데이터 내보내기", "connectSetting": "연결 설정", "connected": "연결됨", @@ -52,13 +51,13 @@ "port": "포트", "hostPlaceHolder": "호스트 입력", "portPlaceHolder": "포트 번호 입력", - "noDamageDetail": "데미지 세부 정보가 없습니다", - "noCharactersInLineup": "라인업에 캐릭터가 없습니다", + "noDamageDetail": "피해 세부사항 없음", + "noCharactersInLineup": "편성에 캐릭터 없음", "noTurns": "아직 턴이 없습니다", - "type": "타입 ", + "type": "유형", "warrior": "파괴", "knight": "보존", - "mage": "지식", + "mage": "지혜", "priest": "풍요", "rouge": "사냥", "shaman": "조화", @@ -70,6 +69,40 @@ "physical": "물리", "quantum": "양자", "thunder": "번개", - "wind": "바람" + "wind": "바람", + "cycle": "사이클", + "wave": "웨이브", + "hp": "HP", + "atk": "공격력", + "speed": "속도", + "critRate": "치명타 확률", + "critDmg": "치명타 피해", + "breakEffect": "파괴 효과", + "effectRes": "효과 저항", + "energyRegenerationRate": "에너지 회복 속도", + "effectHitRate": "효과 적중률", + "outgoingHealingBoost": "치유량 증가", + "fireDmgBoost": "불 속성 피해 증가", + "iceDmgBoost": "얼음 속성 피해 증가", + "imaginaryDmgBoost": "허수 속성 피해 증가", + "physicalDmgBoost": "물리 속성 피해 증가", + "quantumDmgBoost": "양자 속성 피해 증가", + "thunderDmgBoost": "번개 속성 피해 증가", + "windDmgBoost": "바람 속성 피해 증가", + "pursued": "추가 피해", + "true damage": "관통 피해", + "follow-up": "추가 공격 피해", + "elemental damage": "파괴 및 초파괴 피해", + "dot": "지속 피해", + "damagePerCycle": "사이클당 피해", + "damagePerCycleAndWave": "사이클 및 웨이브당 피해", + "damagePerWave": "웨이브당 피해", + "lastTurn": "마지막 턴", + "qte": "QTE 스킬", + "mazenormal": "일반 미궁", + "level": "레벨", + "relics": "유물", + "eidolons": "에이돌론", + "lightcones": "광추" } } diff --git a/messages/vi.json b/messages/vi.json index 26ab286..df0049d 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -6,9 +6,8 @@ "DataAnalysisPage": { "useSkill": "Sử dụng kỹ năng", "totalDamage": "Tổng sát thương", - "damagePerAV": "Sát thương mỗi AV", - "totalAV": "Tổng AV", - "damagerPerCycle": "Sát thương mỗi vòng", + "damagePerAV": "Sát thương/giá trị hành động", + "totalAV": "Tổng giá trị hành động", "skillType": "Loại kỹ năng", "skillName": "Tên kỹ năng", "actionValue": "Giá trị hành động", @@ -70,6 +69,40 @@ "physical": "Vật Lý", "quantum": "Lượng Tử", "thunder": "Lôi", - "wind": "Phong" + "wind": "Phong", + "cycle": "Vòng", + "wave": "Đợt quái", + "hp": "Máu", + "atk": "Tấn công", + "speed": "Tốc độ", + "critRate": "Tỉ lệ chí mạng", + "critDmg": "Sát thương chí mạng", + "breakEffect": "Tấn công kích phá", + "effectRes": "Kháng hiệu ứng", + "energyRegenerationRate": "Hiệu quả nạp", + "effectHitRate": "Chinh xác hiệu ứng", + "outgoingHealingBoost": "Hiệu quả trị liệu", + "fireDmgBoost": "Tăng sát thương hỏa", + "iceDmgBoost": "Tăng sát thương băng", + "imaginaryDmgBoost": "Tăng sát thương số ảo", + "physicalDmgBoost": "Tăng sát thương vật lý", + "quantumDmgBoost": "Tăng sát thương lượng tử", + "thunderDmgBoost": "Tăng sát thương lôi", + "windDmgBoost": "Tăng sát thương phong", + "pursued": "Sát thương kèm theo", + "true damage": "Sát thương chuẩn", + "follow-up": "Sát thương theo sau", + "elemental damage": "Sát thương phá vỡ và siêu phá vỡ", + "dot": "Sát Thương Duy Trì", + "damagePerCycle": "Sát thương mỗi vòng", + "damagePerCycleAndWave": "Sát thương mỗi vòng và đợt", + "damagePerWave": "Sát thương mỗi đợt", + "lastTurn": "Lượt cuối", + "qte": "Kỹ năng QTE", + "mazenormal": "Mê cung (Thường)", + "level": "Cấp độ", + "relics": "Di vật", + "eidolons": "Tinh hồn", + "lightcones": "Nón Ánh sáng" } } diff --git a/messages/zh.json b/messages/zh.json index 6a6ab73..9d41bd8 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -8,7 +8,6 @@ "totalDamage": "总伤害", "damagePerAV": "每行动值伤害", "totalAV": "总行动值", - "damagerPerCycle": "每轮伤害", "skillType": "技能类型", "skillName": "技能名称", "actionValue": "行动值", @@ -70,6 +69,40 @@ "physical": "物理", "quantum": "量子", "thunder": "雷", - "wind": "风" + "wind": "风", + "cycle": "回合", + "wave": "波次", + "hp": "生命值", + "atk": "攻击力", + "speed": "速度", + "critRate": "暴击率", + "critDmg": "暴击伤害", + "breakEffect": "击破特攻", + "effectRes": "效果抵抗", + "energyRegenerationRate": "能量回复效率", + "effectHitRate": "效果命中", + "outgoingHealingBoost": "治疗量加成", + "fireDmgBoost": "火属性伤害提高", + "iceDmgBoost": "冰属性伤害提高", + "imaginaryDmgBoost": "虚数属性伤害提高", + "physicalDmgBoost": "物理属性伤害提高", + "quantumDmgBoost": "量子属性伤害提高", + "thunderDmgBoost": "雷属性伤害提高", + "windDmgBoost": "风属性伤害提高", + "pursued": "附加伤害", + "true damage": "真实伤害", + "follow-up": "追加攻击", + "elemental damage": "击破与超击破伤害", + "dot": "持续伤害 ", + "damagePerCycle": "每轮伤害", + "damagePerCycleAndWave": "每轮/波伤害", + "damagePerWave": "每波伤害", + "lastTurn": "最后一回合", + "qte": "QTE技能", + "mazenormal": "普通迷宫", + "level": "等级", + "relics": "遗器", + "eidolons": "星魂", + "lightcones": "光锥" } } diff --git a/next.config.ts b/next.config.ts index 3d38bea..4fa91fb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -18,7 +18,12 @@ const nextConfig: NextConfig = { protocol: 'http', hostname: 'localhost', pathname: '**', - } + }, + { + protocol: 'https', + hostname: 'api.hakush.in', + pathname: '**', + }, ], }, eslint: { diff --git a/package-lock.json b/package-lock.json index c6f4b06..08fb81b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chart.js": "^4.4.9", "chartjs-plugin-datalabels": "^2.2.0", "framer-motion": "^12.7.4", + "html-to-image": "^1.11.13", "next": "15.3.1", "next-intl": "^4.0.2", "react": "^19.0.0", @@ -31,6 +32,7 @@ "daisyui": "^5.0.27", "eslint": "^9", "eslint-config-next": "15.3.1", + "tailwind-scrollbar": "^4.0.2", "tailwindcss": "^4", "typescript": "^5" } @@ -1845,6 +1847,13 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", @@ -4220,6 +4229,12 @@ "node": ">= 0.4" } }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5765,6 +5780,20 @@ "dev": true, "license": "MIT" }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6556,6 +6585,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-scrollbar": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.2.tgz", + "integrity": "sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prism-react-renderer": "^2.4.1" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "4.x" + } + }, "node_modules/tailwindcss": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", diff --git a/package.json b/package.json index 3e480b1..9d93d82 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "chart.js": "^4.4.9", "chartjs-plugin-datalabels": "^2.2.0", "framer-motion": "^12.7.4", + "html-to-image": "^1.11.13", "next": "15.3.1", "next-intl": "^4.0.2", "react": "^19.0.0", @@ -32,6 +33,7 @@ "daisyui": "^5.0.27", "eslint": "^9", "eslint-config-next": "15.3.1", + "tailwind-scrollbar": "^4.0.2", "tailwindcss": "^4", "typescript": "^5" } diff --git a/src/app/globals.css b/src/app/globals.css index 3549807..ccafb87 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,27 @@ @import "tailwindcss"; + + @plugin "daisyui" { themes: winter --default, night --prefersdark, cupcake, coffee; } +@plugin 'tailwind-scrollbar' { + nocompatible: true; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: #9ca3af; + border-radius: 0; +} + +::-webkit-scrollbar-button { + display: none; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2295b12..0af71de 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -39,7 +39,7 @@ export default async function RootLayout({ -
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index c233fba..5b3769c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -18,9 +18,8 @@ export default function Home() { totalAV, totalDamage, damagePerAV, + turnHistory } = useBattleDataStore(); - const [modeBar, setModeBar] = useState<0 | 1 | 2>(1); - const [modeLine, setModeLine] = useState<0 | 1>(1); const [expandedCharts, setExpandedCharts] = useState([]); const toggleExpand = (chartId: string) => { @@ -45,53 +44,50 @@ export default function Home() { return (
-
+
-
-
+
+ +
- {transI18n("totalDamage")}: {Number(totalDamage).toFixed(2)} + {transI18n("totalDamage")} +
{Number(totalDamage).toFixed(2)}
- {transI18n("totalAV")}: {Number(totalAV).toFixed(2)} + {transI18n("totalAV")} +
{Number(totalAV).toFixed(2)}
- {transI18n("damagePerAV")}: {Number(damagePerAV).toFixed(2)} + {transI18n("damagePerAV")} +
{Number(damagePerAV).toFixed(2)}
+
+
+ {transI18n("totalTurn")} +
{turnHistory.filter(it => it.avatarId && it.avatarId != -1).length}
+
- {expandedCharts.includes('chart1') ? ( -
-
- -
- +
+
+
- ) : ( -
-
- -
- -
- )} + +
-
- {[0, 1].map((m) => ( - - ))} -
- +
- {expandedCharts.includes('chart3') ? ( -
-
- -
- + +
+
+
- ) : ( -
-
- -
- -
- )} + +
-
- {[0, 1, 2].map((m) => ( - - ))} -
- +
diff --git a/src/components/actionbar/index.tsx b/src/components/actionbar/index.tsx index 88b3d10..994ea02 100644 --- a/src/components/actionbar/index.tsx +++ b/src/components/actionbar/index.tsx @@ -2,18 +2,19 @@ import useAvatarDataStore from "@/stores/avatarDataStore"; import useBattleDataStore from "@/stores/battleDataStore"; import useLocaleStore from "@/stores/localeStore"; -import { AvatarType } from "@/types"; -import { TurnBattleInfo } from "@/types/mics"; +import {attackTypeToString, AvatarHakushiType} from "@/types"; +import { SkillBattleInfo } from "@/types/mics"; import { useEffect, useState, useRef } from "react"; import { useTranslations } from "next-intl"; import { motion } from "framer-motion"; import { getNameChar } from "@/helper"; +import Image from "next/image"; export default function ActionBar() { - const [selectTurn, setSelectTurn] = useState(null); - const [selectAvatar, setSelectAvatar] = useState(null); + const [selectTurn, setSelectTurn] = useState(null); + const [selectAvatar, setSelectAvatar] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); - const { turnHistory } = useBattleDataStore(); + const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore(); const { listAvatar } = useAvatarDataStore(); const { locale } = useLocaleStore(); const transI18n = useTranslations("DataAnalysisPage"); @@ -21,13 +22,13 @@ export default function ActionBar() { const contentStyle: React.CSSProperties = { - + display: 'flex', flexDirection: 'column', alignItems: 'center', }; - const handleShow = (modalId: string, avatar: AvatarType, turn: TurnBattleInfo) => { + const handleShow = (modalId: string, avatar: AvatarHakushiType, turn: SkillBattleInfo) => { const modal = document.getElementById(modalId) as HTMLDialogElement | null; if (modal) { setSelectAvatar(avatar); @@ -61,10 +62,10 @@ export default function ActionBar() { // Scroll to the bottom when new turns are added useEffect(() => { - if (turnListRef.current && turnHistory.length > 0) { + if (turnListRef.current && skillHistory.length > 0) { turnListRef.current.scrollTop = turnListRef.current.scrollHeight; } - }, [turnHistory.length]); + }, [skillHistory.length]); return (
@@ -76,49 +77,31 @@ export default function ActionBar() { > {transI18n("actionBar")} +
+
+ + + + {waveIndex}/{maxWave} +
+
+ + + + {cycleIndex} +
+
- -
- {turnHistory.length === 0 ? ( + {skillHistory.length === 0 ? (

{transI18n("noTurns")}

) : ( - turnHistory.map((turn, index) => { + skillHistory.map((turn, index) => { const data = listAvatar.find(it => it.id === turn.avatarId.toString()); if (!data) return null; const text = getNameChar(locale, data); @@ -135,20 +118,22 @@ export default function ActionBar() { >
- {text}
- +
{getNameChar(locale, data)}
- {`${transI18n("useSkill")}: ${transI18n(turn.skillType.toLowerCase())}`} + {`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`}
{`${transI18n("totalDamage")}: ${turn.totalDamage.toFixed(2)}`} @@ -157,7 +142,7 @@ export default function ActionBar() {
); - + }) )}
@@ -189,7 +174,7 @@ export default function ActionBar() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} - className="mt-4 w-full custom-scrollbar" + className="mt-4 w-full" >

{transI18n("characterInformation")}

@@ -205,10 +190,11 @@ export default function ActionBar() {

- {getNameChar(locale,
@@ -218,7 +204,7 @@ export default function ActionBar() {

{transI18n("skillType")}

-

{transI18n(selectTurn?.skillType.toLowerCase())}

+

{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}

{transI18n("skillName")}

@@ -226,10 +212,21 @@ export default function ActionBar() {
+
+
+

{transI18n("cycle")}

+

{turnHistory[selectTurn?.turnBattleId].cycleIndex}

+
+
+

{transI18n("wave")}

+

{turnHistory[selectTurn?.turnBattleId].waveIndex}

+
+
+

{transI18n("actionValue")}

-

{selectTurn?.actionValue.toFixed(2)}

+

{turnHistory[selectTurn?.turnBattleId].actionValue.toFixed(2)}

{transI18n("totalDamage")}

@@ -239,15 +236,26 @@ export default function ActionBar() {

{transI18n("damageDetails")}

- {selectTurn?.damageDetail && selectTurn.damageDetail.length > 0 ? ( -
+ {selectTurn?.damageDetail?.length > 0 ? ( +
{selectTurn.damageDetail.map((detail, idx) => ( -

{detail.toFixed(2)}

+
+ + {detail.damage.toFixed(2)} + + + {transI18n(attackTypeToString(detail?.damage_type).toLowerCase())} + +
))}
) : (

{transI18n("noDamageDetail")}

)} +
)} diff --git a/src/components/card/characterCard.tsx b/src/components/card/characterCard.tsx index b611780..857b439 100644 --- a/src/components/card/characterCard.tsx +++ b/src/components/card/characterCard.tsx @@ -2,10 +2,10 @@ import { getNameChar } from '@/helper'; import useLocaleStore from '@/stores/localeStore'; -import { AvatarType } from '@/types'; +import { AvatarHakushiType } from '@/types'; interface CharacterCardProps { - data: AvatarType + data: AvatarHakushiType } export function parseRuby(text: string): string { diff --git a/src/components/card/showCaseCard.tsx b/src/components/card/showCaseCard.tsx new file mode 100644 index 0000000..4ee66ae --- /dev/null +++ b/src/components/card/showCaseCard.tsx @@ -0,0 +1,665 @@ +"use client"; +import React, { useState, useRef} from 'react'; + +export default function ShowCaseInfo() { + const [imageUrl, setImageUrl] = useState('https://api.hakush.in/hsr/UI/avatardrawcard/1001.webp'); + const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 100 }); + const ref = useRef(null) + + return ( +
+ +
+
+ Showcase Background +
+ +
+
+
+
+
+ Character Preview +
+
+
+
+ {[ + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-you-eidolon_icon_small.webp", + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-it-eidolon_icon_small.webp", + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-everything-eidolon_icon_small.webp", + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forfeit-again-eidolon_icon_small.webp", + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forget-again-eidolon_icon_small.webp", + "https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/just-like-this-always-eidolon_icon_small.webp" + ].map((src, index) => ( +
+ Rank Icon +
+ +
+
+ ))} +
+
+
+
+ Background + +
+
+
+
+ March 7th + Element Icon +
+
+ Path Icon + Preservation +
+
+ Lv. 80 + / + 80 +
+
+
+
+ Path Icon +
+
+
+
+ Skill Icon + 6 / 6 + Basic ATK +
+
+
+
+ Skill Icon + 10 / 10 + Skill +
+
+
+
+
+ Skill Icon + 10 / 10 + Ultimate +
+
+
+
+
+ Skill Icon + 8 / 8 + Talent +
+
+
+
+ Skill Icon + 5 / 5 + Technique +
+
+
+
+ +
+
+ {/* First Column */} +
+ Icon 1001101 +
+ Icon 1001101 + Icon 1001101 +
+
+ + {/* Second Column */} +
+ Icon 1001102 +
+ Icon 1001102 + Icon 1001102 +
+
+ + {/* Third Column */} +
+ Icon 1001103 +
+ Icon 1001103 + Icon 1001103 + Icon 1001103 +
+
+ + {/* Fourth Column */} +
+
+ Icon 1001201 + Icon 1001201 + Icon 1001201 +
+
+
+
+ +
+
+ Light Cone Preview + Light Cone Rarity Icon +
+
+ On the Fall of an Aeon +
+
+ V +
+
+ Lv. 80 + / + 80 +
+
+
+
+ Attribute Icon + 529 +
+
+ Attribute Icon + 397 +
+
+ Attribute Icon + 1058 +
+
+
+
+ +
+
+ +
+
+
+
+ Stat Icon + HP +
+
+
2942
+
+ +
+
+ Stat Icon + ATK +
+
+
3212
+
+ +
+
+ Stat Icon + DEF +
+
+
1255
+
+ +
+
+ Stat Icon + SPD +
+
+
106
+
+ +
+
+ Stat Icon + CRIT Rate +
+
+
16.7%
+
+ +
+
+ Stat Icon + CRIT DMG +
+
+
79.2%
+
+ +
+
+ Stat Icon + Break Effect +
+
+
186.9%
+
+ +
+
+ Stat Icon + Effect RES +
+
+
4.0%
+
+ +
+
+ Stat Icon + Energy Regeneration Rate +
+
+
0.0%
+
+ +
+
+ Stat Icon + Effect Hit Rate +
+
+
20.3%
+
+ +
+
+ Stat Icon + Outgoing Healing Boost +
+
+
0.0%
+
+ +
+
+ Stat Icon + Ice DMG Boost +
+
+
6.4%
+
+
+ +
+ +
+
+ Prisoner in Deep Confinement +
+ 2 +
+
+
+ Watchmaker, Master of Dream Machinations +
+ 2 +
+
+
+ Talia: Kingdom of Banditry +
+ 2 +
+
+
+
+
+
+
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 705 + +15 +
+
+
+
+
+ Sub Affix Icon + +7.8% +
+
+
+
+ Sub Affix Icon + +3.2% +
+
+
+
+ Sub Affix Icon + +17.5% +
+
+
+
+ Sub Affix Icon + +15.6% +
+
+
+
+ +
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 352 + +15 +
+
+
+
+
+ Sub Affix Icon + +6.9% +
+
+
+
+ Sub Affix Icon + +11.7% +
+
+
+
+ Sub Affix Icon + +3.9% +
+
+
+
+ Sub Affix Icon + +23.3% +
+
+
+
+ +
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 43.2% + +15 +
+
+
+
+
+ Sub Affix Icon + +57 +
+
+
+
+ Sub Affix Icon + +3.9% +
+
+
+
+ Sub Affix Icon + +2.6% +
+
+
+
+ Sub Affix Icon + +19.4% +
+
+
+
+ +
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 18.9% + +15 +
+
+
+
+
+ Sub Affix Icon + +5.2% +
+
+
+
+ Sub Affix Icon + +4.3% +
+
+
+
+ Sub Affix Icon + +2.7% +
+
+
+
+ Sub Affix Icon + +34 +
+
+
+
+
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 18.9% + +15 +
+
+
+
+
+ Sub Affix Icon + +5.2% +
+
+
+
+ Sub Affix Icon + +4.3% +
+
+
+
+ Sub Affix Icon + +2.7% +
+
+
+
+ Sub Affix Icon + +34 +
+
+
+
+
+
+ Relic Icon + Relic Rarity Icon +
+
+ Main Affix Icon + 18.9% + +15 +
+
+
+
+
+ Sub Affix Icon + +5.2% +
+
+
+
+ Sub Affix Icon + +4.3% +
+
+
+
+ Sub Affix Icon + +2.7% +
+
+
+
+ Sub Affix Icon + +34 +
+
+
+
+
+
+ +
+
+ +
+
+ +
+ ); +} diff --git a/src/components/chart/damageLineForAll.tsx b/src/components/chart/damageLineForAll.tsx index ff666c4..52b5944 100644 --- a/src/components/chart/damageLineForAll.tsx +++ b/src/components/chart/damageLineForAll.tsx @@ -13,24 +13,29 @@ import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll'; import useAvatarDataStore from '@/stores/avatarDataStore'; import useLocaleStore from '@/stores/localeStore'; import { getNameChar } from '@/helper'; +import { useState } from 'react'; +import { useTranslations } from 'next-intl'; ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend); const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c']; -export default function MultiCharLineChart({ mode = 0 }: { mode?: 0 | 1 }) { +export default function MultiCharLineChart() { + const [mode, setMode] = useState<1 | 2>(2); const dataByAvatar = useDamageLinesForAll(mode); const avatarIds = Object.keys(dataByAvatar).map(Number); const { listAvatar } = useAvatarDataStore() const { locale } = useLocaleStore(); - + const transI18n = useTranslations("DataAnalysisPage") + + const data = { datasets: avatarIds.map((id, idx) => ({ label: getNameChar(locale, listAvatar.find(it => it.id == id.toString())), data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => { return { - x: x.toFixed(2), - y: y.toFixed(2) + x: x.toFixed(2), + y: y.toFixed(2) } }), borderColor: colors[idx % colors.length], @@ -54,12 +59,27 @@ export default function MultiCharLineChart({ mode = 0 }: { mode?: 0 | 1 }) { beginAtZero: true, }, }, - plugins: { - datalabels: { - display: false, - }, - }, }; - return ; + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ + +
+ + ) } diff --git a/src/components/chart/damageLineForOne.tsx b/src/components/chart/damageLineForOne.tsx index 5e47195..8f95719 100644 --- a/src/components/chart/damageLineForOne.tsx +++ b/src/components/chart/damageLineForOne.tsx @@ -2,7 +2,6 @@ import { Line } from "react-chartjs-2"; import { ChartOptions } from "chart.js"; import { useDamageLineForOne } from "@/hooks/useDamageLineForOne"; - import { Chart as ChartJS, LineElement, @@ -15,6 +14,7 @@ import { Filler, } from "chart.js"; import { useTranslations } from "next-intl"; +import { useState } from "react"; ChartJS.register( LineElement, @@ -27,11 +27,9 @@ ChartJS.register( Filler ); -ChartJS.defaults.set('plugins.datalabels', { - color: '#FE777B' -}); -export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mode?: 0 | 1 }) { +export function DamageLineForOne({ avatarId }: { avatarId: number; }) { + const [mode, setMode] = useState<0 | 1>(1); const dataRaw = useDamageLineForOne(avatarId, mode); const transI18n = useTranslations("DataAnalysisPage"); const data = { @@ -52,9 +50,6 @@ export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mod responsive: true, plugins: { legend: { display: true }, - datalabels: { - display: false, - }, }, scales: { x: { title: { display: true, text: transI18n("actionValue") } }, @@ -63,5 +58,23 @@ export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mod }; - return ; + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ +
+ ) } diff --git a/src/components/chart/damagePerAvatarForAll.tsx b/src/components/chart/damagePerAvatarForAll.tsx index fba67cf..755888f 100644 --- a/src/components/chart/damagePerAvatarForAll.tsx +++ b/src/components/chart/damagePerAvatarForAll.tsx @@ -1,4 +1,5 @@ -"use client"; +'use client'; +import { useState } from 'react'; import { getNameChar } from '@/helper'; import useAvatarDataStore from '@/stores/avatarDataStore'; import useBattleDataStore from '@/stores/battleDataStore'; @@ -9,11 +10,11 @@ import { CategoryScale, LinearScale, Tooltip, - Legend, + Legend } from 'chart.js'; -import { useTranslations } from 'next-intl'; import { Bar } from 'react-chartjs-2'; - +import { useTranslations } from 'next-intl'; +import {attackTypeToString} from "@/types"; ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); @@ -25,56 +26,153 @@ const colorPalette = [ 'rgba(153, 102, 255, 0.6)', 'rgba(255, 159, 64, 0.6)', 'rgba(199, 199, 199, 0.6)', -]; - -const borderPalette = colorPalette.map((color) => color.replace('0.6', '1')); + 'rgba(255, 99, 255, 0.6)', + 'rgba(0, 200, 83, 0.6)', + 'rgba(255, 112, 67, 0.6)', + 'rgba(63, 81, 181, 0.6)', + 'rgba(0, 188, 212, 0.6)', + 'rgba(233, 30, 99, 0.6)', + 'rgba(124, 179, 66, 0.6)', + 'rgba(255, 235, 59, 0.6)', + 'rgba(158, 158, 158, 0.6)', + 'rgba(121, 85, 72, 0.6)', + 'rgba(96, 125, 139, 0.6)', + 'rgba(100, 181, 246, 0.6)', + 'rgba(255, 171, 145, 0.6)', + 'rgba(174, 213, 129, 0.6)', + 'rgba(255, 245, 157, 0.6)' + ]; + +const borderPalette = colorPalette.map((c) => c.replace('0.6', '1')); export default function DamagePerAvatarForAll() { const transI18n = useTranslations("DataAnalysisPage"); - const { lineup, turnHistory } = useBattleDataStore() - const { listAvatar } = useAvatarDataStore() + const { lineup, skillHistory } = useBattleDataStore(); + const { listAvatar } = useAvatarDataStore(); const { locale } = useLocaleStore(); - const damageByAvatar = lineup.map((avatar) => { - const turns = turnHistory.filter((turn) => turn.avatarId === avatar.avatarId); - const totalDmg = turns.reduce((sum, turn) => sum + turn.totalDamage, 0); - const char = listAvatar.find(it => it.id === avatar.avatarId.toString()) - if (!char) return + + const [mode, setMode] = useState(2); + + const avatarMap = lineup.map((avatar) => { + const char = listAvatar.find(it => it.id === avatar.avatarId.toString()); + if (!char) return undefined; return { avatarId: avatar.avatarId, - damage: totalDmg, avatarName: getNameChar(locale, char) }; - }); - - const data = { - labels: damageByAvatar.map((item) => item?.avatarName), - datasets: [ + }).filter(Boolean) as { avatarId: number, avatarName: string }[]; + + const labels = avatarMap.map(a => a.avatarName); + + let datasets: Array<{ + label: string; + data: number[]; + backgroundColor: string | string[]; + borderColor: string | string[]; + borderWidth: number; + stack?: string; + }> = []; + + if (mode === 1) { + datasets = [ { label: transI18n("totalDamage"), - data: damageByAvatar.map((item) => item?.damage), - backgroundColor: damageByAvatar.map((_, idx) => colorPalette[idx % colorPalette.length]), - borderColor: damageByAvatar.map((_, idx) => borderPalette[idx % borderPalette.length]), - borderWidth: 1, - }, - ], - }; + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId) + .reduce((sum, s) => sum + s.totalDamage, 0); + return total; + }), + backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]), + borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]), + borderWidth: 1 + } + ]; + } + + if (mode === 2) { + const damageTypesSet = new Set(); + avatarMap.forEach(({ avatarId }) => { + skillHistory.filter(s => s.avatarId === avatarId).forEach(s => + s.damageDetail?.forEach(d => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase()))) + ); + }); + const damageTypes = Array.from(damageTypesSet); + + datasets = damageTypes.map((dt, i) => ({ + label: dt, + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId) + .flatMap(s => s.damageDetail || []) + .filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt) + .reduce((sum, d) => sum + d.damage, 0); + return total; + }), + backgroundColor: colorPalette[i % colorPalette.length], + borderColor: borderPalette[i % borderPalette.length], + borderWidth: 1, + stack: transI18n('damage') + })); + } + + if (mode === 3) { + const skillTypesSet = new Set(); + skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase()))); + const skillTypes = Array.from(skillTypesSet); + + datasets = skillTypes.map((st, i) => ({ + label: st, + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st) + .reduce((sum, s) => sum + s.totalDamage, 0); + return total; + }), + backgroundColor: colorPalette[i % colorPalette.length], + borderColor: borderPalette[i % borderPalette.length], + borderWidth: 1, + stack: transI18n('skill') + })); + } const options = { responsive: true, scales: { y: { beginAtZero: true, + stacked: mode !== 1, ticks: { precision: 0, }, }, - }, - plugins: { - datalabels: { - display: false, + x: { + stacked: mode !== 1, }, }, + }; - return ; + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ + +
+ + ); } diff --git a/src/components/chart/damagePerCycleForAll.tsx b/src/components/chart/damagePerCycleForAll.tsx index 68968f4..f1b90cc 100644 --- a/src/components/chart/damagePerCycleForAll.tsx +++ b/src/components/chart/damagePerCycleForAll.tsx @@ -2,36 +2,33 @@ import { Bar } from "react-chartjs-2"; import { ChartOptions } from "chart.js"; import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle"; - import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; import { useTranslations } from "next-intl"; +import { useState } from "react"; ChartJS.register(BarElement, ArcElement, RadialLinearScale); -export default function DamagePerCycleForAll({ - mode, -}: { - mode: 0 | 1 | 2; -}) { +export default function DamagePerCycleForAll() { + const [mode, setMode] = useState<0 | 1 | 2>(0); const dataRaw = useDamagePerCycleForAll(mode); const transI18n = useTranslations("DataAnalysisPage"); const data = { labels: dataRaw.map(d => d.x), datasets: [ { - label: mode === 0 ? `${transI18n("damagerPerCycle")} (100av)` : mode === 1 ? `${transI18n("damagerPerCycle")} (150av | 100v)` : `${transI18n("damagerPerCycle")} (150av | 150av | 100v)`, + label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, data: dataRaw.map(d => d.y), - backgroundColor: "rgba(255,99,132,0.6)", + backgroundColor: dataRaw.map((_, i) => + ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] + ), }, ], + }; const options: ChartOptions<"bar"> = { responsive: true, plugins: { legend: { display: true }, - datalabels: { - display: false, - }, }, scales: { x: { title: { display: true, text: transI18n("cycleCount") } }, @@ -40,5 +37,24 @@ export default function DamagePerCycleForAll({ }; - return ; + return ( +
+
+ {[ + { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ ; +
+ ) } diff --git a/src/components/chart/damagePerCycleForOne.tsx b/src/components/chart/damagePerCycleForOne.tsx index 24e6e93..21f80a2 100644 --- a/src/components/chart/damagePerCycleForOne.tsx +++ b/src/components/chart/damagePerCycleForOne.tsx @@ -5,24 +5,26 @@ import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle"; import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; import { useTranslations } from "next-intl"; +import { useState } from "react"; ChartJS.register(BarElement, ArcElement, RadialLinearScale); export function DamagePerCycleForOne({ avatarId, - mode, }: { avatarId: number; - mode: 0 | 1 | 2; }) { + const [mode, setMode] = useState<0 | 1 | 2>(0); const dataRaw = useDamagePerCycleForOne(avatarId, mode); const transI18n = useTranslations("DataAnalysisPage"); const data = { labels: dataRaw.map(d => d.x), datasets: [ { - label: mode === 0 ? `${transI18n("damagerPerCycle")} (100av)` : mode === 1 ? `${transI18n("damagerPerCycle")} (150av | 100v)` : `${transI18n("damagerPerCycle")} (150av | 150av | 100v)`, + label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, data: dataRaw.map(d => d.y), - backgroundColor: "rgba(255,99,132,0.6)", + backgroundColor: dataRaw.map((_, i) => + ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] + ), }, ], }; @@ -31,9 +33,6 @@ export function DamagePerCycleForOne({ responsive: true, plugins: { legend: { display: true }, - datalabels: { - display: false, - }, }, scales: { x: { title: { display: true, text: transI18n("cycleCount") } }, @@ -42,5 +41,24 @@ export function DamagePerCycleForOne({ }; - return ; + return ( +
+
+ {[ + { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ +
+ ) } diff --git a/src/components/chart/damagePercentForAll.tsx b/src/components/chart/damagePercentForAll.tsx index d625756..38bbb80 100644 --- a/src/components/chart/damagePercentForAll.tsx +++ b/src/components/chart/damagePercentForAll.tsx @@ -11,22 +11,40 @@ import useAvatarDataStore from '@/stores/avatarDataStore'; import useLocaleStore from '@/stores/localeStore'; import { getNameChar } from '@/helper'; import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType'; ChartJS.register(ArcElement, Tooltip, Legend); -const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6']; +const colors = [ + '#f87171', '#34d399', '#60a5fa', '#facc15', + '#a78bfa', '#fb923c', '#f472b6', '#10b981', + '#fcd34d', '#c084fc', '#f97316', '#ec4899', + '#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e' +]; export default function DamagePercentChartForAll() { - const data = useDamagePercentPerAvatar(); - const { listAvatar } = useAvatarDataStore() + const [mode, setMode] = useState<1 | 2>(1); + const damageByAvatar = useDamagePercentPerAvatar(); + const damageByType = useDamagePercentByType(); + const { listAvatar } = useAvatarDataStore(); const { locale } = useLocaleStore(); const transI18n = useTranslations("DataAnalysisPage"); + const chartData = { - labels: data.map(d => getNameChar(locale, listAvatar.find(it => it.id == d.avatarId.toString()))), + labels: (mode === 1 + ? damageByAvatar.map(d => + getNameChar(locale, listAvatar.find(it => it.id == d.avatarId.toString())) + ) + : damageByType.map(d => transI18n(d.type.toLowerCase())) + ), datasets: [ { label: '% ' + transI18n("damage"), - data: data.map(d => d.percent.toFixed(2)), + data: (mode === 1 + ? damageByAvatar.map(d => d.percent.toFixed(2)) + : damageByType.map(d => d.percent.toFixed(2)) + ), backgroundColor: colors, borderWidth: 1, }, @@ -47,5 +65,27 @@ export default function DamagePercentChartForAll() { }, }; - return ; + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ +
+ +
+
+ ); + } diff --git a/src/components/chart/skillBarChart.tsx b/src/components/chart/skillBarChart.tsx index 0919aad..240c70e 100644 --- a/src/components/chart/skillBarChart.tsx +++ b/src/components/chart/skillBarChart.tsx @@ -1,58 +1,70 @@ 'use client'; -import { getNameChar } from '@/helper'; -import { useSkillDamageByAvatar } from '@/hooks/useSkillDamageStats'; -import useAvatarDataStore from '@/stores/avatarDataStore'; -import useLocaleStore from '@/stores/localeStore'; -import { - Chart as ChartJS, - BarElement, - CategoryScale, - LinearScale, - Tooltip, - Legend, -} from 'chart.js'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; -import { useTranslations } from 'next-intl'; +import { useState } from 'react'; import { Bar } from 'react-chartjs-2'; +import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js'; +import { useTranslations } from 'next-intl'; +import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; +import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; -ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend, ChartDataLabels); + +ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); export default function SkillBarChart({ avatarId }: { avatarId: number }) { - const { labels, damageValues } = useSkillDamageByAvatar(avatarId); - const transI18n = useTranslations("DataAnalysisPage"); - const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c']; - const { listAvatar } = useAvatarDataStore() - const { locale } = useLocaleStore(); - const data = { - labels: labels.map(it => transI18n(it.toLowerCase())), - datasets: [ - { - label: getNameChar(locale, listAvatar.find(it => it.id === avatarId.toString())), - data: damageValues, - backgroundColor: colors.slice(0, labels.length), - borderColor: '#111', - borderWidth: 1, - }, - ], - }; + const [mode, setMode] = useState<1 | 2>(2); - const options = { - responsive: true, - scales: { - y: { - beginAtZero: true, - ticks: { precision: 0 }, - }, - }, - plugins: { - legend: { - display: false, - }, - datalabels: { - display: false, - }, - }, - }; + const transI18n = useTranslations('DataAnalysisPage'); + const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId); + const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId); - return ; + const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c']; + + const data = { + labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)), + datasets: [ + { + label: mode === 1 + ? transI18n('skill') + : transI18n('damage'), + data: mode === 1 ? skillDamageValues : typeDamageValues, + backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length), + borderColor: '#111', + borderWidth: 1, + }, + ], + }; + + const options = { + responsive: true, + scales: { + y: { + beginAtZero: true, + ticks: { precision: 0 }, + }, + }, + plugins: { + legend: { + display: false, + }, + }, + }; + + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ +
+ ); } diff --git a/src/components/chart/skillPieChart.tsx b/src/components/chart/skillPieChart.tsx index 64e2a8e..49bd61e 100644 --- a/src/components/chart/skillPieChart.tsx +++ b/src/components/chart/skillPieChart.tsx @@ -1,59 +1,75 @@ -"use client"; -import { useSkillDamageByAvatar } from '@/hooks/useSkillDamageStats'; -import { - Chart as ChartJS, - ArcElement, - Tooltip, - Legend, -} from 'chart.js'; +'use client'; +import { useState } from 'react'; import { Pie } from 'react-chartjs-2'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; +import { + Chart as ChartJS, + ArcElement, + Tooltip, + Legend, +} from 'chart.js'; import { useTranslations } from 'next-intl'; +import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; +import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; -ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels); +ChartJS.register(ArcElement, Tooltip, Legend); + +const COLORS = [ + '#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c', + '#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80', + '#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac', +]; export default function SkillPieChart({ avatarId }: { avatarId: number }) { - const { labels, damageValues } = useSkillDamageByAvatar(avatarId); - const transI18n = useTranslations("DataAnalysisPage"); - const total = damageValues.reduce((sum, val) => sum + val, 0); + const [mode, setMode] = useState<1 | 2>(2); + const transI18n = useTranslations("DataAnalysisPage"); - const labelsWithPercent = labels.map((label, index) => { - const value = damageValues[index]; - const percent = total ? ((value / total) * 100).toFixed(1) : '0.0'; - return `${transI18n(label.toLowerCase())} (${percent}%)`; - }); + const skillData = useSkillDamageForAvatar(avatarId); + const typeData = useDamageByTypeForAvatar(avatarId); - const data = { - labels: labelsWithPercent, - datasets: [ - { - data: damageValues, - backgroundColor: [ - '#f87171', // red - '#facc15', // yellow - '#34d399', // green - '#60a5fa', // blue - '#a78bfa', // purple - '#fb923c', // orange - ], - borderColor: '#fff', - borderWidth: 2, - }, - ], - }; + const { labels, damageValues } = mode === 1 ? skillData : typeData; + const total = damageValues.reduce((sum, val) => sum + val, 0); - return ( - - ); + const labelsWithPercent = labels.map((label, index) => { + const value = damageValues[index]; + const percent = total ? ((value / total) * 100).toFixed(1) : '0.0'; + return `${transI18n(label.toLowerCase())} (${percent}%)`; + }); + + const data = { + labels: labelsWithPercent, + datasets: [ + { + data: damageValues, + backgroundColor: COLORS.slice(0, labels.length), + borderColor: '#fff', + borderWidth: 1, + }, + ], + }; + + return ( +
+
+ {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
+ +
+ ); } diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 1aa2084..c873d58 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -7,7 +7,6 @@ import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@ import useBattleDataStore from "@/stores/battleDataStore"; import useLocaleStore from "@/stores/localeStore"; import useSocketStore from "@/stores/socketSettingStore"; -import { BattleDataStateJson } from "@/types/mics"; import { motion } from "framer-motion"; import { useTranslations } from "next-intl"; import Link from "next/link"; @@ -24,15 +23,7 @@ const themes = [ export default function Header() { const { changeTheme } = useChangeTheme() const { locale, setLocale } = useLocaleStore() - const { - totalAV, - totalDamage, - damagePerAV, - turnHistory, - lineup, - loadBattleDataFromJSON, - } = useBattleDataStore() - + const { loadBattleDataFromJSON } = useBattleDataStore() const router = useRouter() const transI18n = useTranslations("DataAnalysisPage") const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore(); @@ -201,7 +192,7 @@ export default function Header() {
  • @@ -261,7 +252,7 @@ export default function Header() {
  • diff --git a/src/components/lineupbar/index.tsx b/src/components/lineupbar/index.tsx index ef4d6ba..de9b5b8 100644 --- a/src/components/lineupbar/index.tsx +++ b/src/components/lineupbar/index.tsx @@ -4,7 +4,7 @@ import useBattleDataStore from "@/stores/battleDataStore"; import CharacterCard from "../card/characterCard"; import { useTranslations } from "next-intl"; import { useState, useEffect } from "react"; -import { AvatarType } from "@/types"; +import { AvatarHakushiType } from "@/types"; import useLocaleStore from "@/stores/localeStore"; import { getNameChar } from '@/helper/getNameChar'; import SkillBarChart from "../chart/skillBarChart"; @@ -13,19 +13,17 @@ import { motion } from "framer-motion"; import { DamageLineForOne } from "../chart/damageLineForOne"; import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne"; import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData"; +import Image from "next/image"; +// import ShowCaseInfo from "../card/showCaseCard"; export default function LineupBar() { - const [selectedCharacter, setSelectedCharacter] = useState(null); + const [selectedCharacter, setSelectedCharacter] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const transI18n = useTranslations("DataAnalysisPage"); - const { lineup } = useBattleDataStore(); + const { lineup, turnHistory, dataAvatar } = useBattleDataStore(); const { listAvatar } = useAvatarDataStore(); const { locale } = useLocaleStore(); - const [modeBar, setModeBar] = useState<0 | 1 | 2>(1); - const [modeLine, setModeLine] = useState<0 | 1>(1); - - const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0); const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0) @@ -34,7 +32,7 @@ export default function LineupBar() { lineup.some(av => av.avatarId.toString() === item.id) ); - const handleShow = (modalId: string, item: AvatarType) => { + const handleShow = (modalId: string, item: AvatarHakushiType) => { const modal = document.getElementById(modalId) as HTMLDialogElement | null; if (modal) { setSelectedCharacter(item); @@ -76,62 +74,47 @@ export default function LineupBar() { {transI18n("lineupInfo")} -
    +
    +
    + + + + + {transI18n("lastTurn")}: {getNameChar(locale, listAvatar.find(it => it.id === turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString()))} +
    +
    + + +
    {lineupAvatars.length === 0 ? (

    {transI18n("noCharactersInLineup")}

    ) : ( -
    - - +
    - {lineupAvatars.map((item, index) => ( - handleShow("character_detail_modal", item)} - > - - - ))} + {lineupAvatars.map((item, index) => { + const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1; + const isLastTurn = item.id === lastTurnAvatarId.toString(); + + return ( + handleShow("character_detail_modal", item)} + className={`cursor-pointer flex-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)]" : "" + }`} + > + + + ); + })} +
    - )} {/* Character Detail Modal */} @@ -165,7 +148,7 @@ export default function LineupBar() {

    {transI18n("characterInformation")}

    -
    +

    {transI18n("id")}: {selectedCharacter.id}

    @@ -173,11 +156,13 @@ export default function LineupBar() { {transI18n("path")}: {transI18n(selectedCharacter.baseType.toLowerCase())} {selectedCharacter.baseType && ( - {selectedCharacter.baseType.toLowerCase()} )}

    @@ -186,31 +171,77 @@ export default function LineupBar() {

    {transI18n("element")}: - {transI18n(selectedCharacter.damageType.toLowerCase())} - {selectedCharacter.damageType && ( - {selectedCharacter.damageType.toLowerCase()} - )} + {selectedCharacter.damageType.toLowerCase()}

    + + {(() => { + const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id); + if (!avatar) return null; + const relicIds = avatar.relics.map(item => { + const relicIdStr = item.relic_id.toString().slice(1); + const slot = relicIdStr.slice(-1); + return `${item.relic_set_id}_${slot}`; + }); + return ( + <> +

    + {transI18n("level")}: {avatar.level} +

    +

    + {transI18n("eidolons")}: {avatar?.data?.rank} +

    +

    + {transI18n("lightcones")}: + {avatar?.Lightcone?.item_id?.toString() +

    +

    + {transI18n("relics")}: +

    + {relicIds.map(it => ( + {avatar?.Lightcone?.item_id?.toString() + ))} +
    +

    + + ); + })()} +
    -
    -
    - {selectedCharacter && ( - {getNameChar(locale, - )}
    + {getNameChar(locale,
    + {/*
    +

    {transI18n("characterInformation")}

    + +
    */}

    {transI18n("totalTurn")}: {totalTurn.toFixed(2)}

    @@ -218,52 +249,30 @@ export default function LineupBar() {

    {transI18n("totalDamage")}: {totalDamage.toFixed(2)}

    - -
    +

    {transI18n("skillDamageBreakdown")}

    -
    +

    {transI18n("damageOverTime")}

    -
    - {[0, 1].map((m) => ( - - ))} -
    +
    - +
    -
    +

    {transI18n("skillUsageDistribution")}

    -
    +
    -

    {transI18n("damagerPerCycle")}

    -
    - {[0, 1, 2].map((m) => ( - - ))} -
    +

    {transI18n("damagePerCycle")}

    - +
    diff --git a/src/helper/exportDataBattle.ts b/src/helper/exportDataBattle.ts index 0d819b5..d4ca205 100644 --- a/src/helper/exportDataBattle.ts +++ b/src/helper/exportDataBattle.ts @@ -1,15 +1,42 @@ +import useBattleDataStore from "@/stores/battleDataStore"; import { BattleDataStateJson } from "@/types/mics"; export const exportBattleData = ( - data: BattleDataStateJson, - filename = 'battle_data.json' - ) => { - const dataStr = JSON.stringify(data, null, 2); - const blob = new Blob([dataStr], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); - }; \ No newline at end of file + filename = 'battle_data.json' +) => { + const { lineup, + turnHistory, + skillHistory, + totalAV, + totalDamage, + damagePerAV, + cycleIndex, + waveIndex, + dataAvatar, + maxWave, + maxCycle + } = useBattleDataStore.getState(); + + const data: BattleDataStateJson = { + lineup, + turnHistory, + skillHistory, + dataAvatar, + totalAV, + totalDamage, + damagePerAV, + cycleIndex, + waveIndex, + maxWave, + maxCycle + } + + const dataStr = JSON.stringify(data, null, 2); + const blob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +}; \ No newline at end of file diff --git a/src/helper/getNameChar.ts b/src/helper/getNameChar.ts index 0d52d1b..42169f8 100644 --- a/src/helper/getNameChar.ts +++ b/src/helper/getNameChar.ts @@ -1,8 +1,8 @@ import { listCurrentLanguage } from "@/lib/constant"; -import { AvatarType } from "@/types"; +import { AvatarHakushiType } from "@/types"; -export function getNameChar(locale: string, data: AvatarType | undefined): string { +export function getNameChar(locale: string, data: AvatarHakushiType | undefined): string { if (!data) { return "" } diff --git a/src/hooks/useCalcAvatarData.ts b/src/hooks/useCalcAvatarData.ts index b4f98a4..bd7aefc 100644 --- a/src/hooks/useCalcAvatarData.ts +++ b/src/hooks/useCalcAvatarData.ts @@ -2,13 +2,13 @@ import useBattleDataStore from "@/stores/battleDataStore"; import { useMemo } from "react"; export function useCalcTotalDmgAvatar(avatarId: number) { - const { turnHistory } = useBattleDataStore.getState(); + const { skillHistory } = useBattleDataStore.getState(); return useMemo(() => { - return turnHistory + return skillHistory .filter(t => t.avatarId === avatarId) .reduce((sum, turn) => sum + turn.totalDamage, 0); - }, [avatarId, turnHistory]); + }, [avatarId, skillHistory]); } diff --git a/src/hooks/useDamageLineForOne.ts b/src/hooks/useDamageLineForOne.ts index 34e8cba..d34fded 100644 --- a/src/hooks/useDamageLineForOne.ts +++ b/src/hooks/useDamageLineForOne.ts @@ -2,16 +2,16 @@ import useBattleDataStore from "@/stores/battleDataStore"; import { useMemo } from "react"; export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) { - const { turnHistory } = useBattleDataStore.getState(); + const { skillHistory, turnHistory } = useBattleDataStore.getState(); return useMemo(() => { const map = new Map(); - for (const turn of turnHistory) { - if (turn.avatarId !== avatarId) continue; - - const prev = map.get(turn.actionValue) || 0; - map.set(turn.actionValue, prev + turn.totalDamage); + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + const actionValue = turnHistory[skill.turnBattleId].actionValue + const prev = map.get(actionValue) || 0; + map.set(actionValue, prev + skill.totalDamage); } const points = Array.from(map.entries()) @@ -27,5 +27,5 @@ export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) { } return points.map(p => ({ x: p.x, y: Number(p.y.toFixed(2)) })); - }, [avatarId, turnHistory, mode]); + }, [avatarId, skillHistory, turnHistory, mode]); } diff --git a/src/hooks/useDamageLinesForAll.ts b/src/hooks/useDamageLinesForAll.ts index 787e5cb..4431be3 100644 --- a/src/hooks/useDamageLinesForAll.ts +++ b/src/hooks/useDamageLinesForAll.ts @@ -1,20 +1,21 @@ import useBattleDataStore from "@/stores/battleDataStore"; import { useMemo } from "react"; -export function useDamageLinesForAll(mode: 0 | 1 = 0) { - const { turnHistory } = useBattleDataStore.getState(); +export function useDamageLinesForAll(mode: 1 | 2 = 1) { + const { turnHistory, skillHistory } = useBattleDataStore.getState(); return useMemo(() => { const avatarMap = new Map>(); - for (const turn of turnHistory) { - if (!avatarMap.has(turn.avatarId)) { - avatarMap.set(turn.avatarId, new Map()); + for (const skill of skillHistory) { + if (!avatarMap.has(skill.avatarId)) { + avatarMap.set(skill.avatarId, new Map()); } - const charMap = avatarMap.get(turn.avatarId)!; - const prev = charMap.get(turn.actionValue) || 0; - charMap.set(turn.actionValue, prev + turn.totalDamage); + const charMap = avatarMap.get(skill.avatarId)!; + const actionValue = turnHistory[skill.turnBattleId].actionValue + const prev = charMap.get(actionValue) || 0; + charMap.set(actionValue, prev + skill.totalDamage); } const result: Record = {}; @@ -27,7 +28,7 @@ export function useDamageLinesForAll(mode: 0 | 1 = 0) { if (mode === 1) { let cumulative = 0; result[avatarId] = points.map(p => { - cumulative += p.y; + cumulative += Number(p.y); return { x: p.x, y: Number(cumulative.toFixed(2)) }; }); } else { @@ -39,5 +40,5 @@ export function useDamageLinesForAll(mode: 0 | 1 = 0) { } return result; - }, [turnHistory, mode]); + }, [turnHistory, skillHistory, mode]); } diff --git a/src/hooks/useDamagePerCycle.ts b/src/hooks/useDamagePerCycle.ts index d9e489f..ab4c1f0 100644 --- a/src/hooks/useDamagePerCycle.ts +++ b/src/hooks/useDamagePerCycle.ts @@ -1,79 +1,69 @@ import useBattleDataStore from "@/stores/battleDataStore"; +import { useTranslations } from "next-intl"; import { useMemo } from "react"; type Mode = 0 | 1 | 2; -function getChunkIndex(av: number, mode: Mode): number { - let sum = 0; - let idx = 0; - - if (mode === 0) { - while (sum <= av) { - sum += 100; - idx++; - } - } else if (mode === 1) { - if (av < 150) return 0; - sum = 150; - idx = 1; - while (sum <= av) { - sum += 100; - idx++; - } - } else { - if (av < 150) return 0; - if (av < 300) return 1; - sum = 300; - idx = 2; - while (sum <= av) { - sum += 100; - idx++; - } - } - - return idx - 1; -} - export function useDamagePerCycleForOne(avatarId: number, mode: Mode) { - const { turnHistory } = useBattleDataStore.getState(); + const { skillHistory, turnHistory, maxCycle } = useBattleDataStore.getState(); + const transI18n = useTranslations("DataAnalysisPage"); + return useMemo(() => { + const damageMap = new Map(); + + skillHistory + .filter(s => s.avatarId === avatarId) + .forEach(s => { + const turn = turnHistory[s.turnBattleId]; + if (!turn) return; - return useMemo(() => { - const damageMap = new Map(); + let key = ''; + if (mode === 0) { + key = `${transI18n('cycle')} ${maxCycle-turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; + } else if (mode === 1) { + key = `${transI18n('cycle')} ${maxCycle-turn.cycleIndex}`; + } else if (mode === 2) { + key = `${transI18n('wave')} ${turn.waveIndex}`; + } - turnHistory - .filter(t => t.avatarId === avatarId) - .forEach(t => { - const idx = getChunkIndex(t.actionValue, mode); - damageMap.set(idx, (damageMap.get(idx) || 0) + t.totalDamage); - }); + damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); + }); - const maxIndex = Math.max(...Array.from(damageMap.keys()), 0); + const result = Array.from(damageMap.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); - return Array.from({ length: maxIndex + 1 }).map((_, i) => ({ - x: `${i + 1}`, - y: damageMap.get(i) || 0, - })); - }, [avatarId, mode, turnHistory]); + return result; + }, [avatarId, mode, skillHistory, turnHistory, transI18n]); } export function useDamagePerCycleForAll(mode: Mode) { - const { turnHistory } = useBattleDataStore.getState(); - + const { skillHistory, turnHistory, maxCycle } = useBattleDataStore.getState(); + const transI18n = useTranslations("DataAnalysisPage"); return useMemo(() => { - const damageMap = new Map(); - - turnHistory.forEach(t => { - const idx = getChunkIndex(t.actionValue, mode); - damageMap.set(idx, (damageMap.get(idx) || 0) + t.totalDamage); - }); - - const maxIndex = Math.max(...Array.from(damageMap.keys()), 0); - - return Array.from({ length: maxIndex + 1 }).map((_, i) => ({ - x: `${i + 1}`, - y: damageMap.get(i) || 0, - })); - }, [mode, turnHistory]); -} \ No newline at end of file + const damageMap = new Map(); + + skillHistory.forEach(s => { + const turn = turnHistory[s.turnBattleId]; + if (!turn) return; + + let key = ''; + if (mode === 0) { + key = `${transI18n('cycle')} ${maxCycle-turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; + } else if (mode === 1) { + key = `${transI18n('cycle')} ${maxCycle-turn.cycleIndex}`; + } else if (mode === 2) { + key = `${transI18n('wave')} ${turn.waveIndex}`; + } + + damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); + }); + + const result = Array.from(damageMap.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); + + return result; + }, [mode, skillHistory, turnHistory, transI18n]); + } \ No newline at end of file diff --git a/src/hooks/useDamagePercentForDamgeType.ts b/src/hooks/useDamagePercentForDamgeType.ts new file mode 100644 index 0000000..74b2451 --- /dev/null +++ b/src/hooks/useDamagePercentForDamgeType.ts @@ -0,0 +1,26 @@ +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useDamagePercentByType() { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgByType = new Map(); + + skillHistory.forEach(skill => { + skill.damageDetail.forEach(detail => { + const type = detail.damage_type; + if (!type) return; + dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage); + }); + }); + + const totalDmg = Array.from(dmgByType.values()).reduce((sum, val) => sum + val, 0); + + return Array.from(dmgByType.entries()).map(([type, dmg]) => ({ + type, + percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, + })); + }, [skillHistory]); +} diff --git a/src/hooks/useDamagePercentPerAvatar.ts b/src/hooks/useDamagePercentPerAvatar.ts index 72757a6..71aee1b 100644 --- a/src/hooks/useDamagePercentPerAvatar.ts +++ b/src/hooks/useDamagePercentPerAvatar.ts @@ -2,12 +2,12 @@ import useBattleDataStore from "@/stores/battleDataStore"; import { useMemo } from "react"; export function useDamagePercentPerAvatar() { - const { turnHistory } = useBattleDataStore.getState(); + const { skillHistory } = useBattleDataStore.getState(); return useMemo(() => { const dmgByAvatar = new Map(); - turnHistory.forEach(t => { + skillHistory.forEach(t => { dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage); }); @@ -17,5 +17,5 @@ export function useDamagePercentPerAvatar() { avatarId, percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, })); - }, [turnHistory]); + }, [skillHistory]); } diff --git a/src/hooks/useDamageTypeForAvatar.ts b/src/hooks/useDamageTypeForAvatar.ts new file mode 100644 index 0000000..42c8f0f --- /dev/null +++ b/src/hooks/useDamageTypeForAvatar.ts @@ -0,0 +1,26 @@ +"use client"; +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useDamageByTypeForAvatar(avatarId: number) { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgMap = new Map(); + + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + + for (const damage of skill.damageDetail) { + const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || ""; + dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage); + } + } + + const labels = Array.from(dmgMap.keys()); + const damageValues = Array.from(dmgMap.values()); + + return { labels, damageValues }; + }, [avatarId, skillHistory]); +} diff --git a/src/hooks/useSkillDamageForAvatar.ts b/src/hooks/useSkillDamageForAvatar.ts new file mode 100644 index 0000000..0b60df5 --- /dev/null +++ b/src/hooks/useSkillDamageForAvatar.ts @@ -0,0 +1,24 @@ +"use client"; +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useSkillDamageForAvatar(avatarId: number) { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgMap = new Map(); + + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + + const type = attackTypeToString(skill.skillType).toLowerCase(); + dmgMap.set(type, (dmgMap.get(type) || 0) + skill.totalDamage); + } + + const labels = Array.from(dmgMap.keys()); + const damageValues = Array.from(dmgMap.values()); + + return { labels, damageValues }; + }, [avatarId, skillHistory]); +} diff --git a/src/hooks/useSkillDamageStats.ts b/src/hooks/useSkillDamageStats.ts deleted file mode 100644 index 60371b1..0000000 --- a/src/hooks/useSkillDamageStats.ts +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; -import { useMemo } from "react"; -import useBattleDataStore from "@/stores/battleDataStore"; - -export function useSkillDamageByAvatar(avatarId: number) { - const { turnHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const skillTypes = ['technique', 'talent', 'basic', 'skill', 'ultimate', 'servant']; - const dmgMap = new Map(); - skillTypes.forEach((type) => dmgMap.set(type, 0)); - - for (const turn of turnHistory) { - const type = turn.skillType.toLowerCase(); - if (turn.avatarId === avatarId && dmgMap.has(type)) { - dmgMap.set(type, dmgMap.get(type)! + turn.totalDamage); - } - } - - const labels = Array.from(dmgMap.keys()); - const damageValues = Array.from(dmgMap.values()); - - return { labels, damageValues }; - }, [avatarId, turnHistory]); -} diff --git a/src/lib/api.ts b/src/lib/api.ts index 61e17b6..03dc351 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,5 +1,5 @@ import useSocketStore from "@/stores/socketSettingStore"; -import { AvatarHakushiType, AvatarType } from "@/types/avatar"; +import { AvatarHakushiType, AvatarHakushiRawType } from "@/types/avatar"; export async function checkConnectTcpApi(): Promise { @@ -22,7 +22,7 @@ export async function checkConnectTcpApi(): Promise { return false } -export async function getCharacterListApi(): Promise { +export async function getCharacterListApi(): Promise { const res = await fetch('/api/hakushin', { method: 'GET', headers: { @@ -35,14 +35,14 @@ export async function getCharacterListApi(): Promise { return []; } - const data: Map = new Map(Object.entries(await res.json())); + const data: Map = new Map(Object.entries(await res.json())); return Array.from(data.entries()).map(([id, it]) => convertAvatar(id, it)); } -function convertAvatar(id: string, item: AvatarHakushiType): AvatarType { +function convertAvatar(id: string, item: AvatarHakushiRawType): AvatarHakushiType { const lang = new Map([ ['en', item.en], ['kr', item.kr], @@ -50,7 +50,7 @@ function convertAvatar(id: string, item: AvatarHakushiType): AvatarType { ['jp', item.jp] ]); - const result: AvatarType = { + const result: AvatarHakushiType = { release: item.release, icon: item.icon, rank: item.rank, diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 6de662c..392ec93 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -2,14 +2,11 @@ import { io, Socket } from "socket.io-client"; import useSocketStore from "@/stores/socketSettingStore"; import { toast } from 'react-toastify'; import useBattleDataStore from "@/stores/battleDataStore"; +import { BattleBeginType } from "@/types"; let socket: Socket | null = null; -const onBattleBegin = () => { - notify("Battle Started!", "info") -} - const notify = (msg: string, type: 'info' | 'success' | 'error' = 'info') => { if (type === 'success') toast.success(msg); else if (type === 'error') toast.error(msg); @@ -25,7 +22,11 @@ export const connectSocket = (): Socket => { onKillService, onDamageService, onBattleEndService, - onTurnBeginService + onTurnBeginService, + onBattleBeginService, + onCreateBattleService, + onUpdateCycleService, + OnUpdateWaveService } = useBattleDataStore.getState(); let url = `${host}:${port}`; @@ -72,21 +73,24 @@ export const connectSocket = (): Socket => { setStatus(true); notify(`Kết nối thành công với Socket ID: ${socket?.id}`, 'success'); }; + const onBattleBegin = (data: BattleBeginType) => { + notify("Battle Started!", "info") + onBattleBeginService(data) + } if (isSocketConnected()) onConnect(); - socket.on("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); - socket.on("TurnEnd", (json) => onTurnEndService(JSON.parse(json))); + socket.on("OnSetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); socket.on("OnTurnEnd", (json) => onTurnEndService(JSON.parse(json))); socket.on("OnUseSkill", (json) => onUseSkillService(JSON.parse(json))); socket.on("OnKill", (json) => onKillService(JSON.parse(json))); socket.on("OnDamage", (json) => onDamageService(JSON.parse(json))); - socket.on('BattleBegin', () => onBattleBegin()); - socket.on('OnBattleBegin', () => onBattleBegin()); - socket.on('TurnBegin', (json) => onTurnBeginService(JSON.parse(json))); + socket.on('OnBattleBegin', (json) => onBattleBegin(JSON.parse(json))); socket.on('OnTurnBegin', (json) => onTurnBeginService(JSON.parse(json))); - socket.on('BattleEnd', (json) => onBattleEndService(JSON.parse(json))); socket.on('OnBattleEnd', (json) => onBattleEndService(JSON.parse(json))); + socket.on('OnUpdateCycle', (json) => onUpdateCycleService(JSON.parse(json))); + socket.on('OnUpdateWave', (json) => OnUpdateWaveService(JSON.parse(json))); + socket.on('OnCreateBattle', (json) => onCreateBattleService(JSON.parse(json))); socket.on("Error", (msg: string) => { console.error("Server Error:", msg); @@ -103,21 +107,28 @@ export const disconnectSocket = (): void => { onKillService, onDamageService, onBattleEndService, - onTurnBeginService + onTurnBeginService, + onBattleBeginService, + onCreateBattleService, + onUpdateCycleService, + OnUpdateWaveService } = useBattleDataStore.getState(); + const onBattleBegin = (data: BattleBeginType) => { + notify("Battle Started!", "info") + onBattleBeginService(data) + } if (socket) { - socket.off("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); - socket.off("TurnEnd", (json) => onTurnEndService(JSON.parse(json))); + socket.off("OnSetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); socket.off("OnTurnEnd", (json) => onTurnEndService(JSON.parse(json))); socket.off("OnUseSkill", (json) => onUseSkillService(JSON.parse(json))); socket.off("OnKill", (json) => onKillService(JSON.parse(json))); socket.off("OnDamage", (json) => onDamageService(JSON.parse(json))); - socket.off('BattleBegin', () => onBattleBegin()); - socket.off('OnBattleBegin', () => onBattleBegin()); - socket.off('TurnBegin', (json) => onTurnBeginService(JSON.parse(json))); + socket.off('OnBattleBegin', (json) => onBattleBegin(JSON.parse(json))); socket.off('OnTurnBegin', (json) => onTurnBeginService(JSON.parse(json))); - socket.off('BattleEnd', (json) => onBattleEndService(JSON.parse(json))); socket.off('OnBattleEnd', (json) => onBattleEndService(JSON.parse(json))); + socket.off('OnUpdateCycle', (json) => onUpdateCycleService(JSON.parse(json))); + socket.off('OnUpdateWave', (json) => OnUpdateWaveService(JSON.parse(json))); + socket.off('OnCreateBattle', (json) => onCreateBattleService(JSON.parse(json))); socket.offAny(); socket.disconnect(); useSocketStore.getState().setStatus(false); diff --git a/src/stores/avatarDataStore.ts b/src/stores/avatarDataStore.ts index 020a3ce..d5711e7 100644 --- a/src/stores/avatarDataStore.ts +++ b/src/stores/avatarDataStore.ts @@ -1,15 +1,15 @@ -import { AvatarType } from '@/types'; +import { AvatarHakushiType } from '@/types'; import { create } from 'zustand' interface AvatarDataState { - listAvatar: AvatarType[]; - setListAvatar: (list: AvatarType[]) => void; + listAvatar: AvatarHakushiType[]; + setListAvatar: (list: AvatarHakushiType[]) => void; } const useAvatarDataStore = create((set) => ({ listAvatar: [], - setListAvatar: (list: AvatarType[]) => set({ listAvatar: list }), + setListAvatar: (list: AvatarHakushiType[]) => set({ listAvatar: list }), })); diff --git a/src/stores/battleDataStore.ts b/src/stores/battleDataStore.ts index 3204b15..38537f2 100644 --- a/src/stores/battleDataStore.ts +++ b/src/stores/battleDataStore.ts @@ -1,14 +1,20 @@ -import { AttackResultType, AvatarSkillType, BattleEndType, KillType, LineUpType, TurnBeginType, TurnEndType } from '@/types'; -import { AvatarBattleInfo, BattleDataStateJson, TurnBattleInfo } from '@/types/mics'; +import { AttackResultType, AvatarAnalysisJson, AvatarSkillType, BattleBeginType, BattleEndType, DamageDetailType, KillType, LineUpType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType } from '@/types'; +import { AvatarBattleInfo, BattleDataStateJson, SkillBattleInfo, TurnBattleInfo } from '@/types/mics'; import { create } from 'zustand' interface BattleDataState { lineup: AvatarBattleInfo[]; turnHistory: TurnBattleInfo[] + skillHistory: SkillBattleInfo[] + dataAvatar: AvatarAnalysisJson[] totalAV: number; totalDamage: number; damagePerAV: number; + cycleIndex: number; + waveIndex: number; + maxWave: number; + maxCycle: number onSetBattleLineupService: (data: LineUpType) => void; onTurnEndService: (data: TurnEndType) => void; @@ -16,47 +22,71 @@ interface BattleDataState { onUseSkillService: (data: AvatarSkillType) => void; onKillService: (data: KillType) => void onDamageService: (data: AttackResultType) => void; - onBattleStartService: () => void; + onBattleBeginService: (data: BattleBeginType) => void; onTurnBeginService: (data: TurnBeginType) => void; + onCreateBattleService: (data: AvatarAnalysisJson[]) => void; + OnUpdateWaveService: (data: UpdateWaveType) => void; + onUpdateCycleService: (data: UpdateCycleType) => void; loadBattleDataFromJSON: (data: BattleDataStateJson) => void; } const useBattleDataStore = create((set, get) => ({ lineup: [], turnHistory: [], + skillHistory: [], + dataAvatar: [], totalAV: 0, totalDamage: 0, damagePerAV: 0, + cycleIndex: 0, + waveIndex: 1, + maxWave: Infinity, + maxCycle: Infinity, loadBattleDataFromJSON: (data: BattleDataStateJson) => { set({ lineup: data.lineup, turnHistory: data.turnHistory, + skillHistory: data.skillHistory, + dataAvatar: data.dataAvatar, totalAV: data.totalAV, totalDamage: data.totalDamage, damagePerAV: data.damagePerAV, + cycleIndex: data.cycleIndex, + waveIndex: data.waveIndex, + maxWave: data.maxWave, + maxCycle: data.maxCycle }) }, - onBattleStartService: () => { + onCreateBattleService: (data: AvatarAnalysisJson[]) => { set({ - lineup: [], - turnHistory: [], - totalAV: 0, - totalDamage: 0, - damagePerAV: 0, + dataAvatar: data + }) + }, + onBattleBeginService: (data: BattleBeginType) => { + const current = get() + const updatedHistory = current.turnHistory.map(it => ({ + ...it, + cycleIndex: data.max_cycles + })) + set({ + maxWave: data.max_waves, + maxCycle: data.max_cycles, + turnHistory: updatedHistory }) }, onDamageService: (data: AttackResultType) => { - const turnHistorys = get().turnHistory - const turnIdx = turnHistorys.findLastIndex(it => it.avatarId === data.attacker.id) - if (turnIdx === -1) { + const skillHistory = get().skillHistory + + const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.id) + if (skillIdx === -1) { return } - const newTh = [...turnHistorys] - newTh[turnIdx].damageDetail.push(data.damage) - newTh[turnIdx].totalDamage += data.damage + const newTh = [...skillHistory] + newTh[skillIdx].damageDetail.push({damage: data.damage, damage_type: data?.damage_type} as DamageDetailType) + newTh[skillIdx].totalDamage += data.damage set({ - turnHistory: newTh, + skillHistory: newTh, totalDamage: get().totalDamage + data.damage, damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV) }) @@ -79,37 +109,54 @@ const useBattleDataStore = create((set, get) => ({ for (const avatar of data.avatars) { lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo) } - set({ + set((state) => ({ lineup: lineups, - turnHistory: [], + turnHistory: [{ + avatarId: -1, + actionValue: 0, + waveIndex: 1, + cycleIndex: state.maxCycle, + } as TurnBattleInfo], + skillHistory: [], totalAV: 0, totalDamage: 0, damagePerAV: 0, - }); + cycleIndex: state.maxCycle, + waveIndex: 1, + })); }, onTurnBeginService: (data: TurnBeginType) => { + set((state) => ({ 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, { + avatarId: data?.turn_owner?.id, + actionValue: data.action_value, + waveIndex: state.waveIndex, + cycleIndex: state.cycleIndex + } as TurnBattleInfo] })) }, onTurnEndService: (data: TurnEndType) => { set((state) => ({ - totalDamage: state.totalDamage === data.total_damage ? data.total_damage : state.totalDamage, - totalAV: data.action_value, - damagePerAV: (state.totalDamage === data.total_damage ? data.total_damage : state.totalDamage) / (data.action_value === 0 ? 1 : data.action_value) + totalDamage: state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage, + currentAV: data.turn_info.action_value, + 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) })); }, onUseSkillService: (data: AvatarSkillType) => { + set((state) => ({ - turnHistory: [...state.turnHistory, { + skillHistory: [...state.skillHistory, { avatarId: data.avatar.id, damageDetail: [], totalDamage: 0, - actionValue: state.totalAV, skillType: data.skill.type, - skillName: data.skill.name - } as TurnBattleInfo] + skillName: data.skill.name, + turnBattleId: state.turnHistory.length-1 + } as SkillBattleInfo] })) }, onBattleEndService: (data: BattleEndType) => { @@ -123,6 +170,16 @@ const useBattleDataStore = create((set, get) => ({ totalAV: data.action_value, damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value) }) + }, + OnUpdateWaveService: (data: UpdateWaveType) => { + set({ + waveIndex: data.wave + }) + }, + onUpdateCycleService: (data: UpdateCycleType) => { + set({ + cycleIndex: data.cycle + }) } })); diff --git a/src/types/attack.ts b/src/types/attack.ts index 2445920..09a443d 100644 --- a/src/types/attack.ts +++ b/src/types/attack.ts @@ -1,6 +1,53 @@ -import { AvatarInfo } from "./lineup"; +import { AvatarType } from "./lineup"; export interface AttackResultType { - attacker: AvatarInfo; + attacker: AvatarType; damage: number; -} \ No newline at end of file + damage_type?: AttackType +} + +export interface DamageDetailType { + damage: number; + damage_type?: AttackType +} + + +export enum AttackType { + Unknown = 0, + Normal = 1, + BPSkill = 2, + Ultra = 3, + QTE = 4, + DOT = 5, + Pursued = 6, + Maze = 7, + MazeNormal = 8, + Insert = 9, + ElementDamage = 10, + Level = 11, + Servant = 12, + TrueDamage = 13 +} + +export function attackTypeToString(type: AttackType | undefined): string { + if (type === undefined) { + return "" + } + switch (type) { + case AttackType.Unknown: return "Talent"; + case AttackType.Normal: return "Basic"; + case AttackType.BPSkill: return "Skill"; + case AttackType.Ultra: return "Ultimate"; + case AttackType.QTE: return "QTE"; + case AttackType.DOT: return "DOT"; + case AttackType.Pursued: return "Pursued"; + case AttackType.Maze: return "Technique"; + case AttackType.MazeNormal: return "MazeNormal"; + case AttackType.Insert: return "Follow-up"; + case AttackType.ElementDamage: return "Elemental Damage"; + case AttackType.Level: return "Level"; + case AttackType.Servant: return "Servant"; + case AttackType.TrueDamage: return "True Damage"; + default: return "Unknown"; + } +} diff --git a/src/types/avatar.ts b/src/types/avatar.ts index efa1096..2cb08dd 100644 --- a/src/types/avatar.ts +++ b/src/types/avatar.ts @@ -1,4 +1,4 @@ -export interface AvatarHakushiType { +export interface AvatarHakushiRawType { release: number; icon: string; rank: string; @@ -11,7 +11,7 @@ export interface AvatarHakushiType { jp: string; } -export interface AvatarType { +export interface AvatarHakushiType { id: string; release: number; icon: string; diff --git a/src/types/battle.ts b/src/types/battle.ts index fb7e8a7..e73521d 100644 --- a/src/types/battle.ts +++ b/src/types/battle.ts @@ -1,14 +1,21 @@ -import { AvatarInfo } from "./lineup"; -import { TurnInfo } from "./turn"; +import { AvatarType } from "./lineup"; +import { TurnInfoType } from "./turn"; export interface BattleEndType { - avatars: AvatarInfo[]; - turn_history: TurnInfo[]; + avatars: AvatarType[]; + turn_history: TurnInfoType[]; + av_history: TurnInfoType[]; turn_count: number; total_damage: number; action_value: number; + stage_id: number; } export interface KillType { - attacker: AvatarInfo; + attacker: AvatarType; } +export interface BattleBeginType { + max_waves: number + max_cycles: number + stage_id: number +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 70e35be..5007d25 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,4 +3,6 @@ export * from "./avatar" export * from "./battle" export * from "./lineup" export * from "./skill" -export * from "./turn" \ No newline at end of file +export * from "./turn" +export * from "./waveAndCycle" +export * from "./srtools" \ No newline at end of file diff --git a/src/types/lineup.ts b/src/types/lineup.ts index edd7687..1db91c0 100644 --- a/src/types/lineup.ts +++ b/src/types/lineup.ts @@ -1,8 +1,8 @@ -export interface AvatarInfo { +export interface AvatarType{ id: number; name: string; } export interface LineUpType { - avatars: AvatarInfo[]; + avatars: AvatarType[]; } \ No newline at end of file diff --git a/src/types/mics.ts b/src/types/mics.ts index b3180ab..69abfb8 100644 --- a/src/types/mics.ts +++ b/src/types/mics.ts @@ -1,21 +1,37 @@ +import {AttackType, DamageDetailType} from "./attack"; +import { AvatarAnalysisJson } from "./srtools"; + export interface AvatarBattleInfo { avatarId: number; isDie: boolean; } +export interface SkillBattleInfo { + avatarId: number; + damageDetail: DamageDetailType[]; + totalDamage: number; + skillType: AttackType; + skillName: string; + turnBattleId: number; +} + export interface TurnBattleInfo { avatarId: number; - damageDetail: number[]; - totalDamage: number; actionValue: number; - skillType: string; - skillName: string; + waveIndex: number; + cycleIndex: number; } export interface BattleDataStateJson { lineup: AvatarBattleInfo[]; turnHistory: TurnBattleInfo[] + skillHistory: SkillBattleInfo[] + dataAvatar: AvatarAnalysisJson[] totalAV: number; totalDamage: number; damagePerAV: number; + maxWave: number; + cycleIndex: number, + waveIndex: number, + maxCycle: number } \ No newline at end of file diff --git a/src/types/skill.ts b/src/types/skill.ts index 8025a79..2cb957d 100644 --- a/src/types/skill.ts +++ b/src/types/skill.ts @@ -1,11 +1,12 @@ -import { AvatarInfo } from "./lineup"; +import { AvatarType } from "./lineup"; +import { AttackType } from "@/types/attack"; export interface SkillInfo { name: string; - type: string; + type: AttackType; } export interface AvatarSkillType { - avatar: AvatarInfo; + avatar: AvatarType; skill: SkillInfo; } \ No newline at end of file diff --git a/src/types/srtools.ts b/src/types/srtools.ts new file mode 100644 index 0000000..0fce82e --- /dev/null +++ b/src/types/srtools.ts @@ -0,0 +1,42 @@ +export interface AvatarAnalysisJson { + owner_uid: number; + avatar_id: number; + data: AvatarData | null; + level: number; + promotion: number; + techniques: number[]; + relics: RelicJson[]; + Lightcone: LightconeJson | null; + sp_value: number; + sp_max: number; +} + +export interface AvatarData { + rank: number; + skills: Record; +} + +export interface SubAffix { + sub_affix_id: number; + count: number; + step: number; +} + +export interface RelicJson { + level: number; + relic_id: number; + relic_set_id: number; + main_affix_id: number; + sub_affixes: SubAffix[]; + internal_uid: number; + equip_avatar: number; +} + +export interface LightconeJson { + level: number; + item_id: number; + equip_avatar: number; + rank: number; + promotion: number; + internal_uid: number; +} diff --git a/src/types/turn.ts b/src/types/turn.ts index b73528a..3b7d1b5 100644 --- a/src/types/turn.ts +++ b/src/types/turn.ts @@ -1,19 +1,20 @@ -import { AvatarInfo } from "./lineup"; +import { AvatarType } from "./lineup"; -export interface TurnInfo { +export interface TurnInfoType { avatars_turn_damage: number[]; total_damage: number; - action_value: number; + action_value: number, + cycle: number, + wave: number, } export interface TurnBeginType { action_value: number; + turn_owner?: AvatarType } export interface TurnEndType { - avatars: AvatarInfo[]; - avatars_damage: number[]; - total_damage: number; - action_value: number; + avatars: AvatarType[]; + turn_info: TurnInfoType } \ No newline at end of file diff --git a/src/types/waveAndCycle.ts b/src/types/waveAndCycle.ts new file mode 100644 index 0000000..b0f5c3d --- /dev/null +++ b/src/types/waveAndCycle.ts @@ -0,0 +1,7 @@ +export interface UpdateWaveType { + wave: number +} + +export interface UpdateCycleType { + cycle: number +} \ No newline at end of file