feat: implement in-game Damage Analyst & remove role distribution
This commit is contained in:
1 parent
f8c39142eb
commit
b5c4dbec9e
3 files changed
+886
-58
No files matched your search
+110
-15
@@ -59,6 +59,7 @@
|
|||||||
<button class="tab-btn active" data-tab="tab-dashboard" data-i18n="tabDashboard">📊 แดชบอร์ด & จัดทีม</button>
|
<button class="tab-btn active" data-tab="tab-dashboard" data-i18n="tabDashboard">📊 แดชบอร์ด & จัดทีม</button>
|
||||||
<button class="tab-btn" data-tab="tab-avatars" data-i18n="tabAvatars">👥 ตัวละคร</button>
|
<button class="tab-btn" data-tab="tab-avatars" data-i18n="tabAvatars">👥 ตัวละคร</button>
|
||||||
<button class="tab-btn" data-tab="tab-equipment" data-i18n="tabEquipment">🗡️ กรวยแสง & รีลิกส์</button>
|
<button class="tab-btn" data-tab="tab-equipment" data-i18n="tabEquipment">🗡️ กรวยแสง & รีลิกส์</button>
|
||||||
|
<button class="tab-btn" data-tab="tab-damage-analyst" id="tab-btn-damage-analyst" data-i18n="tabDamageAnalyst">💥 Damage Analyst</button>
|
||||||
<button class="tab-btn" data-tab="tab-persistent" id="tab-btn-persistent" data-i18n="tabPersistent">💾 จัดการเซฟ GaC</button>
|
<button class="tab-btn" data-tab="tab-persistent" id="tab-btn-persistent" data-i18n="tabPersistent">💾 จัดการเซฟ GaC</button>
|
||||||
<button class="tab-btn" data-tab="tab-exrank" id="tab-btn-exrank" data-i18n="tabExrank">⚡ สกิลพิเศษ Ex-Rank</button>
|
<button class="tab-btn" data-tab="tab-exrank" id="tab-btn-exrank" data-i18n="tabExrank">⚡ สกิลพิเศษ Ex-Rank</button>
|
||||||
<button class="tab-btn" data-tab="tab-json" data-i18n="tabJson">📝 จัดการไฟล์ JSON</button>
|
<button class="tab-btn" data-tab="tab-json" data-i18n="tabJson">📝 จัดการไฟล์ JSON</button>
|
||||||
@@ -157,7 +158,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="badge-super-tag" id="badge-synergy-coverage" style="background: rgba(56, 189, 248, 0.15); color: var(--accent-cyan); font-weight: 800;">ครอบคลุม 0/7 ธาตุ</span>
|
<span class="badge-super-tag" id="badge-synergy-coverage" style="background: rgba(56, 189, 248, 0.15); color: var(--accent-cyan); font-weight: 800;">ครอบคลุม 0/7 ธาตุ</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="card-subtitle" style="margin-bottom: 0.85rem;" data-i18n="synergySubtitle">วิเคราะห์การครอบคลุม Weakness Break, ความสมดุลของบทบาท และความพร้อมของทีม</p>
|
<p class="card-subtitle" style="margin-bottom: 0.85rem;" data-i18n="synergySubtitle">วิเคราะห์การครอบคลุม Weakness Break และความพร้อมของอุปกรณ์ในทีม</p>
|
||||||
|
|
||||||
<!-- A. Weakness Break Coverage 7 Elements -->
|
<!-- A. Weakness Break Coverage 7 Elements -->
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem;">
|
||||||
@@ -169,20 +170,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- B. Team Role Balance -->
|
<!-- B. Equipment Readiness & Stats Overview -->
|
||||||
<div style="margin-bottom: 1rem;">
|
|
||||||
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;" data-i18n="synergyRoleBalance">
|
|
||||||
🛡️ การกระจายบทบาทในทีม (Team Composition & Roles)
|
|
||||||
</div>
|
|
||||||
<div id="synergy-roles-container" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem;">
|
|
||||||
<!-- Role tags: DPS, Sub-DPS, Buffer/Debuffer, Sustain -->
|
|
||||||
</div>
|
|
||||||
<div id="synergy-advice-box" style="padding: 0.6rem 0.8rem; border-radius: 6px; font-size: 0.8rem; line-height: 1.4; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08);">
|
|
||||||
<!-- Dynamic Advice -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- C. Equipment Readiness & Stats Overview -->
|
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;" data-i18n="synergyEquipmentTitle">
|
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;" data-i18n="synergyEquipmentTitle">
|
||||||
📊 ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย
|
📊 ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย
|
||||||
@@ -206,6 +194,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- C. Quick Damage Analyst Banner -->
|
||||||
|
<div id="synergy-damage-banner" style="margin-top: 1rem; padding: 0.75rem 1rem; border-radius: 8px; background: linear-gradient(135deg, rgba(239, 68, 68, 0.08) 0%, rgba(245, 158, 11, 0.08) 100%); border: 1px solid rgba(239, 68, 68, 0.2); display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s ease;" title="คลิกเพื่อเปิดหน้า Damage Analyst">
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
||||||
|
<span style="font-size: 1.4rem;">💥</span>
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 0.85rem; font-weight: 700; color: #f87171;" id="dashboard-battle-title">Damage Analyst (จับดาเมจจากในเกม)</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);" id="dashboard-battle-summary">ยังไม่มีข้อมูลการต่อสู้ล่าสุด</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.6rem; pointer-events: none;">ดูบทวิเคราะห์ →</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -387,6 +387,101 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- 3.5. Damage Analyst (In-Game Battle Telemetry & Damage Analysis) -->
|
||||||
|
<section id="tab-damage-analyst" class="tab-content">
|
||||||
|
<!-- Toolbar & Control Card -->
|
||||||
|
<div class="card" style="margin-bottom: 1.25rem;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
|
||||||
|
<div>
|
||||||
|
<div class="card-title" style="margin-bottom: 0.25rem;">
|
||||||
|
<span data-i18n="analystTitle">💥 วิเคราะห์ความเสียหายจากการต่อสู้ (Damage Analyst)</span>
|
||||||
|
<span class="badge-super-tag" id="badge-analyst-status" style="background: rgba(34, 197, 94, 0.15); color: #4ade80;">พร้อมรับข้อมูลจากเกม</span>
|
||||||
|
</div>
|
||||||
|
<p class="card-subtitle" style="margin-bottom: 0;" data-i18n="analystSubtitle">
|
||||||
|
ดักจับและวิเคราะห์ดาเมจจากการต่อสู้จริงในเกม Star Rail (ระบบเดียวกับ FireflyGO / Veritas)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
|
||||||
|
<label style="display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: var(--text-muted); cursor: pointer; user-select: none; margin-right: 0.5rem;">
|
||||||
|
<input type="checkbox" id="chk-analyst-autosync" checked style="accent-color: var(--accent-cyan); cursor: pointer;">
|
||||||
|
<span data-i18n="analystAutoSync">ซิงค์อัตโนมัติ</span>
|
||||||
|
</label>
|
||||||
|
<button type="button" class="btn btn-primary" id="btn-analyst-fetch" style="background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);">
|
||||||
|
<span data-i18n="analystFetchBtn">🔄 ดึงดาเมจล่าสุดจากในเกม</span>
|
||||||
|
</button>
|
||||||
|
<input type="file" id="input-analyst-file" accept=".json" style="display: none;">
|
||||||
|
<button type="button" class="btn btn-secondary" id="btn-analyst-import" data-i18n="analystImportBtn">
|
||||||
|
📂 นำเข้า JSON
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" id="btn-analyst-export" data-i18n="analystExportBtn">
|
||||||
|
💾 ส่งออก JSON
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" id="btn-analyst-clear" style="color: #f87171;" data-i18n="analystClearBtn">
|
||||||
|
🗑️ ล้างข้อมูล
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overview 4 Metrics Cards -->
|
||||||
|
<div class="grid-4" style="margin-bottom: 1.25rem;">
|
||||||
|
<div class="card" style="padding: 1.2rem; border-left: 4px solid #ef4444; background: linear-gradient(135deg, rgba(239,68,68,0.06) 0%, rgba(20,20,28,0.7) 100%);">
|
||||||
|
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;" data-i18n="analystTotalDamage">ดาเมจรวมทั้งทีม</div>
|
||||||
|
<div style="font-size: 1.85rem; font-weight: 900; color: #f87171; letter-spacing: -0.5px; margin: 0.25rem 0;" id="analyst-team-damage">0</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);" id="analyst-damage-av">เฉลี่ยต่อรอบ: -</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="padding: 1.2rem; border-left: 4px solid #f59e0b; background: linear-gradient(135deg, rgba(245,158,11,0.06) 0%, rgba(20,20,28,0.7) 100%);">
|
||||||
|
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;" data-i18n="analystCycles">จำนวนรอบ (Cycles)</div>
|
||||||
|
<div style="font-size: 1.85rem; font-weight: 900; color: #fbbf24; letter-spacing: -0.5px; margin: 0.25rem 0;" id="analyst-cycles">0 รอบ</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);" id="analyst-cycles-note">MoC / PF / AS Standard</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="padding: 1.2rem; border-left: 4px solid #38bdf8; background: linear-gradient(135deg, rgba(56,189,248,0.06) 0%, rgba(20,20,28,0.7) 100%);">
|
||||||
|
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;" data-i18n="analystTurns">เทิร์นทั้งหมด</div>
|
||||||
|
<div style="font-size: 1.85rem; font-weight: 900; color: #38bdf8; letter-spacing: -0.5px; margin: 0.25rem 0;" id="analyst-total-turns">0 เทิร์น</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);" id="analyst-auto-turns">เทิร์นอัตโนมัติ: 0</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="padding: 1.2rem; border-left: 4px solid #34d399; background: linear-gradient(135deg, rgba(52,211,153,0.06) 0%, rgba(20,20,28,0.7) 100%);">
|
||||||
|
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;" data-i18n="analystDuration">ระยะเวลาการต่อสู้</div>
|
||||||
|
<div style="font-size: 1.85rem; font-weight: 900; color: #34d399; letter-spacing: -0.5px; margin: 0.25rem 0;" id="analyst-duration">00:00</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);" id="analyst-battle-meta">Stage: - | Battle: -</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Team Damage Share Bar -->
|
||||||
|
<div class="card" style="margin-bottom: 1.25rem;">
|
||||||
|
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
|
||||||
|
<span data-i18n="analystDpsShare">สัดส่วนดาเมจของทีม (Team DPS Share)</span>
|
||||||
|
<span style="font-size: 0.8rem; color: var(--text-muted);" id="analyst-total-share-label">รวม 100%</span>
|
||||||
|
</div>
|
||||||
|
<!-- Progress Bar Container -->
|
||||||
|
<div id="analyst-share-bar-container" style="height: 18px; width: 100%; border-radius: 9px; background: rgba(255,255,255,0.05); overflow: hidden; display: flex; margin-bottom: 0.85rem; border: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
<!-- Dynamic segments filled by JS -->
|
||||||
|
</div>
|
||||||
|
<!-- Legend Container -->
|
||||||
|
<div id="analyst-share-legend" style="display: flex; gap: 1.5rem; flex-wrap: wrap;">
|
||||||
|
<!-- Dynamic legend filled by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Character Breakdown Grid -->
|
||||||
|
<div id="analyst-characters-container" class="grid-2" style="gap: 1.25rem; margin-bottom: 1.5rem;">
|
||||||
|
<!-- Populated dynamically by JS for each avatar in battle -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Target Enemies Encountered -->
|
||||||
|
<div class="card" id="analyst-enemies-card" style="margin-bottom: 1.5rem;">
|
||||||
|
<div class="card-title" style="margin-bottom: 0.75rem;">
|
||||||
|
<span data-i18n="analystEnemyTitle">👾 รายชื่อศัตรูที่ถูกโจมตี (Targets / Enemies)</span>
|
||||||
|
</div>
|
||||||
|
<div id="analyst-enemies-container" style="display: flex; flex-direction: column; gap: 0.6rem;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- 4. Persistent Save Settings (Exclusive to GaC-hkrpg-go) -->
|
<!-- 4. Persistent Save Settings (Exclusive to GaC-hkrpg-go) -->
|
||||||
<section id="tab-persistent" class="tab-content">
|
<section id="tab-persistent" class="tab-content">
|
||||||
<div class="card" style="margin-bottom: 1.5rem;">
|
<div class="card" style="margin-bottom: 1.5rem;">
|
||||||
|
|||||||
@@ -77,12 +77,37 @@ const el = {
|
|||||||
cardTeamSynergy: document.getElementById('card-team-synergy'),
|
cardTeamSynergy: document.getElementById('card-team-synergy'),
|
||||||
badgeSynergyCoverage: document.getElementById('badge-synergy-coverage'),
|
badgeSynergyCoverage: document.getElementById('badge-synergy-coverage'),
|
||||||
synergyElementsContainer: document.getElementById('synergy-elements-container'),
|
synergyElementsContainer: document.getElementById('synergy-elements-container'),
|
||||||
synergyRolesContainer: document.getElementById('synergy-roles-container'),
|
|
||||||
synergyAdviceBox: document.getElementById('synergy-advice-box'),
|
|
||||||
synergyAvgLevel: document.getElementById('synergy-avg-level'),
|
synergyAvgLevel: document.getElementById('synergy-avg-level'),
|
||||||
synergyAvgEidolon: document.getElementById('synergy-avg-eidolon'),
|
synergyAvgEidolon: document.getElementById('synergy-avg-eidolon'),
|
||||||
synergyLcReady: document.getElementById('synergy-lc-ready'),
|
synergyLcReady: document.getElementById('synergy-lc-ready'),
|
||||||
synergyRelicReady: document.getElementById('synergy-relic-ready'),
|
synergyRelicReady: document.getElementById('synergy-relic-ready'),
|
||||||
|
synergyDamageBanner: document.getElementById('synergy-damage-banner'),
|
||||||
|
dashboardBattleTitle: document.getElementById('dashboard-battle-title'),
|
||||||
|
dashboardBattleSummary: document.getElementById('dashboard-battle-summary'),
|
||||||
|
|
||||||
|
// Damage Analyst
|
||||||
|
tabDamageAnalyst: document.getElementById('tab-damage-analyst'),
|
||||||
|
tabBtnDamageAnalyst: document.getElementById('tab-btn-damage-analyst'),
|
||||||
|
badgeAnalystStatus: document.getElementById('badge-analyst-status'),
|
||||||
|
chkAnalystAutosync: document.getElementById('chk-analyst-autosync'),
|
||||||
|
btnAnalystFetch: document.getElementById('btn-analyst-fetch'),
|
||||||
|
inputAnalystFile: document.getElementById('input-analyst-file'),
|
||||||
|
btnAnalystImport: document.getElementById('btn-analyst-import'),
|
||||||
|
btnAnalystExport: document.getElementById('btn-analyst-export'),
|
||||||
|
btnAnalystClear: document.getElementById('btn-analyst-clear'),
|
||||||
|
analystTeamDamage: document.getElementById('analyst-team-damage'),
|
||||||
|
analystDamageAv: document.getElementById('analyst-damage-av'),
|
||||||
|
analystCycles: document.getElementById('analyst-cycles'),
|
||||||
|
analystCyclesNote: document.getElementById('analyst-cycles-note'),
|
||||||
|
analystTotalTurns: document.getElementById('analyst-total-turns'),
|
||||||
|
analystAutoTurns: document.getElementById('analyst-auto-turns'),
|
||||||
|
analystDuration: document.getElementById('analyst-duration'),
|
||||||
|
analystBattleMeta: document.getElementById('analyst-battle-meta'),
|
||||||
|
analystShareBarContainer: document.getElementById('analyst-share-bar-container'),
|
||||||
|
analystShareLegend: document.getElementById('analyst-share-legend'),
|
||||||
|
analystCharactersContainer: document.getElementById('analyst-characters-container'),
|
||||||
|
analystEnemiesContainer: document.getElementById('analyst-enemies-container'),
|
||||||
|
analystEnemiesCard: document.getElementById('analyst-enemies-card'),
|
||||||
|
|
||||||
// GM Command Dispatcher & Live Log Console
|
// GM Command Dispatcher & Live Log Console
|
||||||
cardGmConsole: document.getElementById('card-gm-console'),
|
cardGmConsole: document.getElementById('card-gm-console'),
|
||||||
@@ -1350,51 +1375,13 @@ function renderTeamSynergy(lineups) {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Render Role Tags
|
|
||||||
if (el.synergyRolesContainer) {
|
|
||||||
el.synergyRolesContainer.innerHTML = `
|
|
||||||
<div class="badge-super-tag" style="background: rgba(239, 68, 68, 0.15); color: #f87171; font-weight: 700;">
|
|
||||||
${t('roleDps')} (${roles.dps.length}): ${roles.dps.join(', ') || '-'}
|
|
||||||
</div>
|
|
||||||
<div class="badge-super-tag" style="background: rgba(168, 85, 247, 0.15); color: #c084fc; font-weight: 700;">
|
|
||||||
${t('roleSupport')} (${roles.support.length}): ${roles.support.join(', ') || '-'}
|
|
||||||
</div>
|
|
||||||
<div class="badge-super-tag" style="background: rgba(52, 211, 153, 0.15); color: #34d399; font-weight: 700;">
|
|
||||||
${t('roleSustain')} (${roles.sustain.length}): ${roles.sustain.join(', ') || '-'}
|
|
||||||
</div>
|
|
||||||
${roles.specialist.length ? `
|
|
||||||
<div class="badge-super-tag" style="background: rgba(56, 189, 248, 0.15); color: #38bdf8; font-weight: 700;">
|
|
||||||
${t('roleSpecialist')} (${roles.specialist.length}): ${roles.specialist.join(', ')}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic Advice Text
|
|
||||||
if (el.synergyAdviceBox) {
|
|
||||||
if (activeIds.length === 0) {
|
|
||||||
el.synergyAdviceBox.innerHTML = `<span style="color: var(--text-muted);">${t('adviceEmpty')}</span>`;
|
|
||||||
el.synergyAdviceBox.style.borderColor = 'rgba(255,255,255,0.08)';
|
|
||||||
} else if (roles.sustain.length === 0) {
|
|
||||||
el.synergyAdviceBox.innerHTML = `<span style="color: #fbbf24;">${t('adviceNoSustain')}</span>`;
|
|
||||||
el.synergyAdviceBox.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
|
||||||
} else if (roles.dps.length === 0) {
|
|
||||||
el.synergyAdviceBox.innerHTML = `<span style="color: #38bdf8;">${t('adviceNoDps')}</span>`;
|
|
||||||
el.synergyAdviceBox.style.borderColor = 'rgba(56, 189, 248, 0.4)';
|
|
||||||
} else if (roles.support.length === 0) {
|
|
||||||
el.synergyAdviceBox.innerHTML = `<span style="color: #fbbf24;">${t('adviceNoSupport')}</span>`;
|
|
||||||
el.synergyAdviceBox.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
|
||||||
} else {
|
|
||||||
el.synergyAdviceBox.innerHTML = `<span style="color: #34d399;">${t('adviceBalanced')}</span>`;
|
|
||||||
el.synergyAdviceBox.style.borderColor = 'rgba(52, 211, 153, 0.4)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
if (el.synergyAvgLevel) el.synergyAvgLevel.textContent = activeIds.length ? `Lv.${avgLevel}` : '-';
|
if (el.synergyAvgLevel) el.synergyAvgLevel.textContent = activeIds.length ? `Lv.${avgLevel}` : '-';
|
||||||
if (el.synergyAvgEidolon) el.synergyAvgEidolon.textContent = avgEidolon;
|
if (el.synergyAvgEidolon) el.synergyAvgEidolon.textContent = avgEidolon;
|
||||||
if (el.synergyLcReady) el.synergyLcReady.textContent = activeIds.length ? `${lcCount}/${activeIds.length}` : '-';
|
if (el.synergyLcReady) el.synergyLcReady.textContent = activeIds.length ? `${lcCount}/${activeIds.length}` : '-';
|
||||||
if (el.synergyRelicReady) el.synergyRelicReady.textContent = activeIds.length ? `${fullRelicCount}/${activeIds.length}` : '-';
|
if (el.synergyRelicReady) el.synergyRelicReady.textContent = activeIds.length ? `${fullRelicCount}/${activeIds.length}` : '-';
|
||||||
|
|
||||||
|
updateDashboardBattleBanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -7386,6 +7373,675 @@ async function initGitBranchDisplay() {
|
|||||||
applyBranchToUI(currentGitBranch);
|
applyBranchToUI(currentGitBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// In-Game Battle Telemetry & Damage Analyst
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
const analystState = {
|
||||||
|
currentReport: null,
|
||||||
|
isFetching: false,
|
||||||
|
lastBattleTimestamp: 0,
|
||||||
|
autoSyncInterval: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatNumber(num) {
|
||||||
|
if (num === null || num === undefined || isNaN(num)) return '0';
|
||||||
|
return Math.round(num).toLocaleString('en-US');
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return '00:00';
|
||||||
|
const m = Math.floor(seconds / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFireflySranalysisJson(json) {
|
||||||
|
if (!json || (!json.lineup && !json.skillHistory && !json.turnHistory)) return null;
|
||||||
|
|
||||||
|
const avatarsMap = {};
|
||||||
|
const teamLineup = json.lineup || [];
|
||||||
|
teamLineup.forEach(item => {
|
||||||
|
const aid = item.avatarId;
|
||||||
|
if (aid && aid > 0) {
|
||||||
|
avatarsMap[aid] = {
|
||||||
|
avatar_id: aid,
|
||||||
|
level: 80,
|
||||||
|
rank: 6,
|
||||||
|
total_damage: 0,
|
||||||
|
damage_percent: 0,
|
||||||
|
total_damage_taken: 0,
|
||||||
|
total_heal: 0,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 0,
|
||||||
|
total_turns: 0,
|
||||||
|
extra_turns: 0,
|
||||||
|
attack_type_damage: {},
|
||||||
|
attack_type_max_damage: {},
|
||||||
|
skill_times: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let maxCycles = 0;
|
||||||
|
if (Array.isArray(json.turnHistory)) {
|
||||||
|
json.turnHistory.forEach(turn => {
|
||||||
|
if (turn.cycleIndex > maxCycles) maxCycles = turn.cycleIndex;
|
||||||
|
const aid = turn.avatarId;
|
||||||
|
if (aid && avatarsMap[aid]) {
|
||||||
|
avatarsMap[aid].total_turns++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let teamTotalDmg = 0;
|
||||||
|
if (Array.isArray(json.skillHistory)) {
|
||||||
|
json.skillHistory.forEach(sk => {
|
||||||
|
const aid = sk.avatarId;
|
||||||
|
if (!aid || aid <= 0) return;
|
||||||
|
if (!avatarsMap[aid]) {
|
||||||
|
avatarsMap[aid] = {
|
||||||
|
avatar_id: aid,
|
||||||
|
level: 80,
|
||||||
|
rank: 6,
|
||||||
|
total_damage: 0,
|
||||||
|
damage_percent: 0,
|
||||||
|
total_damage_taken: 0,
|
||||||
|
total_heal: 0,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 0,
|
||||||
|
total_turns: 1,
|
||||||
|
extra_turns: 0,
|
||||||
|
attack_type_damage: {},
|
||||||
|
attack_type_max_damage: {},
|
||||||
|
skill_times: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const av = avatarsMap[aid];
|
||||||
|
const dmg = sk.totalDamage || 0;
|
||||||
|
av.total_damage += dmg;
|
||||||
|
teamTotalDmg += dmg;
|
||||||
|
|
||||||
|
const skType = sk.skillType || 'Other';
|
||||||
|
av.attack_type_damage[skType] = (av.attack_type_damage[skType] || 0) + dmg;
|
||||||
|
|
||||||
|
if (Array.isArray(sk.damageDetail)) {
|
||||||
|
sk.damageDetail.forEach(d => {
|
||||||
|
const detailDmg = d.damage || 0;
|
||||||
|
const dType = d.type || skType;
|
||||||
|
if (detailDmg > (av.attack_type_max_damage[dType] || 0)) {
|
||||||
|
av.attack_type_max_damage[dType] = detailDmg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarsList = Object.values(avatarsMap);
|
||||||
|
avatarsList.forEach(a => {
|
||||||
|
if (teamTotalDmg > 0) {
|
||||||
|
a.damage_percent = (a.total_damage / teamTotalDmg) * 100;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
battle_id: Math.floor(Math.random() * 90000) + 10000,
|
||||||
|
stage_id: 1001,
|
||||||
|
end_status: 'WIN',
|
||||||
|
total_battle_turns: json.turnHistory?.length || 15,
|
||||||
|
total_auto_turns: 0,
|
||||||
|
round_cnt: maxCycles,
|
||||||
|
cost_time: 48,
|
||||||
|
challenge_score: 0,
|
||||||
|
team_total_damage: teamTotalDmg,
|
||||||
|
team_total_heal: 0,
|
||||||
|
team_total_shield: 0,
|
||||||
|
avatars: avatarsList,
|
||||||
|
monsters: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatestBattleReport(silent = false) {
|
||||||
|
if (analystState.isFetching) return;
|
||||||
|
analystState.isFetching = true;
|
||||||
|
try {
|
||||||
|
const data = await apiRequest('/api/battle_report', 'GET');
|
||||||
|
if (data && data.has_report && data.report) {
|
||||||
|
const isNew = data.report.timestamp !== analystState.lastBattleTimestamp;
|
||||||
|
analystState.currentReport = data.report;
|
||||||
|
analystState.lastBattleTimestamp = data.report.timestamp;
|
||||||
|
renderDamageAnalyst();
|
||||||
|
updateDashboardBattleBanner();
|
||||||
|
if (!silent) {
|
||||||
|
showToast(t('analystToastFetched'), 'success');
|
||||||
|
} else if (isNew) {
|
||||||
|
showToast(t('analystToastFetched'), 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!silent) {
|
||||||
|
showToast(data?.message || t('analystNoBattle'), 'info');
|
||||||
|
}
|
||||||
|
if (!analystState.currentReport) {
|
||||||
|
renderEmptyDamageAnalyst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!silent) {
|
||||||
|
showToast(`Error: ${err.message}`, 'error');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
analystState.isFetching = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEmptyDamageAnalyst() {
|
||||||
|
if (el.analystTeamDamage) el.analystTeamDamage.textContent = '0';
|
||||||
|
if (el.analystCycles) el.analystCycles.textContent = `0 ${t('analystCycles')}`;
|
||||||
|
if (el.analystTotalTurns) el.analystTotalTurns.textContent = `0 ${t('analystTurns')}`;
|
||||||
|
if (el.analystDuration) el.analystDuration.textContent = '00:00';
|
||||||
|
if (el.analystDamageAv) el.analystDamageAv.textContent = 'เฉลี่ย: -';
|
||||||
|
if (el.analystBattleMeta) el.analystBattleMeta.textContent = 'Stage: - | Battle: -';
|
||||||
|
|
||||||
|
if (el.badgeAnalystStatus) {
|
||||||
|
el.badgeAnalystStatus.textContent = 'พร้อมรับข้อมูลจากเกม';
|
||||||
|
el.badgeAnalystStatus.style.background = 'rgba(34, 197, 94, 0.15)';
|
||||||
|
el.badgeAnalystStatus.style.color = '#4ade80';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.analystShareBarContainer) {
|
||||||
|
el.analystShareBarContainer.innerHTML = '<div style="width: 100%; background: rgba(255,255,255,0.05); height: 100%;"></div>';
|
||||||
|
}
|
||||||
|
if (el.analystShareLegend) {
|
||||||
|
el.analystShareLegend.innerHTML = '';
|
||||||
|
}
|
||||||
|
if (el.analystCharactersContainer) {
|
||||||
|
el.analystCharactersContainer.innerHTML = `
|
||||||
|
<div style="grid-column: 1/-1; text-align: center; padding: 3rem 1rem; color: var(--text-muted);">
|
||||||
|
<div style="font-size: 3rem; margin-bottom: 0.75rem;">⚔️</div>
|
||||||
|
<div style="font-size: 1.1rem; font-weight: 700; color: var(--text-main); margin-bottom: 0.5rem;">${t('analystNoBattle')}</div>
|
||||||
|
<div style="display: flex; gap: 0.5rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap;">
|
||||||
|
<button type="button" class="btn btn-primary" id="btn-empty-analyst-fetch" style="background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);">${t('analystFetchBtn')}</button>
|
||||||
|
<button type="button" class="btn btn-secondary" id="btn-empty-analyst-demo">✨ โหลดตัวอย่างการต่อสู้ (Demo)</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const btnF = document.getElementById('btn-empty-analyst-fetch');
|
||||||
|
if (btnF) btnF.addEventListener('click', () => fetchLatestBattleReport(false));
|
||||||
|
const btnD = document.getElementById('btn-empty-analyst-demo');
|
||||||
|
if (btnD) btnD.addEventListener('click', () => loadDemoBattleReport());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.analystEnemiesCard) {
|
||||||
|
el.analystEnemiesCard.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDamageAnalyst() {
|
||||||
|
const report = analystState.currentReport;
|
||||||
|
if (!report) {
|
||||||
|
renderEmptyDamageAnalyst();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const teamDmg = report.team_total_damage || 0;
|
||||||
|
if (el.analystTeamDamage) el.analystTeamDamage.textContent = formatNumber(teamDmg);
|
||||||
|
if (el.analystCycles) {
|
||||||
|
el.analystCycles.textContent = (report.round_cnt !== undefined) ? `${report.round_cnt} รอบ (${report.round_cnt}-Cycle)` : '0 รอบ';
|
||||||
|
}
|
||||||
|
if (el.analystTotalTurns) el.analystTotalTurns.textContent = `${report.total_battle_turns || 0} เทิร์น`;
|
||||||
|
if (el.analystDuration) el.analystDuration.textContent = formatDuration(report.cost_time);
|
||||||
|
if (el.analystDamageAv) {
|
||||||
|
el.analystDamageAv.textContent = (report.round_cnt > 0)
|
||||||
|
? `เฉลี่ย: ${(teamDmg / report.round_cnt).toFixed(0).toLocaleString()} / รอบ`
|
||||||
|
: `เฉลี่ย: ${(teamDmg / Math.max(1, report.total_battle_turns || 1)).toFixed(0).toLocaleString()} / เทิร์น`;
|
||||||
|
}
|
||||||
|
if (el.analystBattleMeta) {
|
||||||
|
el.analystBattleMeta.textContent = `Stage: ${report.stage_id || '-'} | Battle: #${report.battle_id || '-'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.badgeAnalystStatus) {
|
||||||
|
if (report.end_status === 'WIN') {
|
||||||
|
el.badgeAnalystStatus.textContent = t('analystStatusWin');
|
||||||
|
el.badgeAnalystStatus.style.background = 'rgba(34, 197, 94, 0.15)';
|
||||||
|
el.badgeAnalystStatus.style.color = '#4ade80';
|
||||||
|
} else if (report.end_status === 'QUIT') {
|
||||||
|
el.badgeAnalystStatus.textContent = t('analystStatusQuit');
|
||||||
|
el.badgeAnalystStatus.style.background = 'rgba(245, 158, 11, 0.15)';
|
||||||
|
el.badgeAnalystStatus.style.color = '#fbbf24';
|
||||||
|
} else {
|
||||||
|
el.badgeAnalystStatus.textContent = t('analystStatusLose');
|
||||||
|
el.badgeAnalystStatus.style.background = 'rgba(239, 68, 68, 0.15)';
|
||||||
|
el.badgeAnalystStatus.style.color = '#f87171';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = ['#ef4444', '#38bdf8', '#34d399', '#fbbf24', '#c084fc', '#ec4899'];
|
||||||
|
|
||||||
|
// Team Share Bar
|
||||||
|
if (el.analystShareBarContainer) {
|
||||||
|
if (report.avatars && report.avatars.length > 0) {
|
||||||
|
el.analystShareBarContainer.innerHTML = report.avatars.map((a, idx) => {
|
||||||
|
const color = colors[idx % colors.length];
|
||||||
|
const pct = a.damage_percent || (teamDmg > 0 ? (a.total_damage / teamDmg) * 100 : 0);
|
||||||
|
const info = getAvatarInfo(a.avatar_id);
|
||||||
|
return `<div style="width: ${pct}%; background: ${color}; height: 100%; transition: width 0.4s ease;" title="${info.name}: ${pct.toFixed(1)}% (${formatNumber(a.total_damage)})"></div>`;
|
||||||
|
}).join('');
|
||||||
|
} else {
|
||||||
|
el.analystShareBarContainer.innerHTML = '<div style="width: 100%; background: rgba(255,255,255,0.05); height: 100%;"></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legend
|
||||||
|
if (el.analystShareLegend) {
|
||||||
|
if (report.avatars && report.avatars.length > 0) {
|
||||||
|
el.analystShareLegend.innerHTML = report.avatars.map((a, idx) => {
|
||||||
|
const color = colors[idx % colors.length];
|
||||||
|
const info = getAvatarInfo(a.avatar_id);
|
||||||
|
const pct = a.damage_percent || (teamDmg > 0 ? (a.total_damage / teamDmg) * 100 : 0);
|
||||||
|
return `
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.825rem;">
|
||||||
|
<div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; box-shadow: 0 0 6px ${color}88;"></div>
|
||||||
|
<img src="${getAvatarIconSrc(a.avatar_id)}" onerror="handleAvatarImgError(this, ${a.avatar_id})" style="width: 22px; height: 22px; border-radius: 50%; object-fit: cover; border: 1px solid ${color};">
|
||||||
|
<span style="font-weight: 700; color: var(--text-highlight);">${info.name}</span>
|
||||||
|
<span style="font-weight: 800; color: ${color};">${pct.toFixed(1)}%</span>
|
||||||
|
<span style="color: var(--text-muted); font-size: 0.75rem;">(${formatNumber(a.total_damage)})</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
} else {
|
||||||
|
el.analystShareLegend.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Characters Detailed Cards Grid
|
||||||
|
if (el.analystCharactersContainer) {
|
||||||
|
if (!report.avatars || report.avatars.length === 0) {
|
||||||
|
el.analystCharactersContainer.innerHTML = `
|
||||||
|
<div style="grid-column: 1/-1; text-align: center; padding: 2.5rem; color: var(--text-muted);">
|
||||||
|
${t('analystNoBattle')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
el.analystCharactersContainer.innerHTML = report.avatars.map((a, idx) => {
|
||||||
|
const color = colors[idx % colors.length];
|
||||||
|
const info = getAvatarInfo(a.avatar_id);
|
||||||
|
const pct = a.damage_percent || (teamDmg > 0 ? (a.total_damage / teamDmg) * 100 : 0);
|
||||||
|
|
||||||
|
let maxHit = 0;
|
||||||
|
if (a.attack_type_max_damage) {
|
||||||
|
for (const key in a.attack_type_max_damage) {
|
||||||
|
if (a.attack_type_max_damage[key] > maxHit) maxHit = a.attack_type_max_damage[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dNormal = a.attack_type_damage?.Normal || 0;
|
||||||
|
const dSkill = a.attack_type_damage?.BPSkill || a.attack_type_damage?.Skill || 0;
|
||||||
|
const dUlt = a.attack_type_damage?.Ultra || a.attack_type_damage?.Ultimate || 0;
|
||||||
|
const dBreak = a.total_break_damage || a.attack_type_damage?.BreakDamage || 0;
|
||||||
|
const dFollowup = (a.attack_type_damage?.Insert || 0) + (a.attack_type_damage?.Pursued || 0);
|
||||||
|
const dDot = a.attack_type_damage?.DOT || 0;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="card" style="padding: 1.25rem; border: 1px solid rgba(255,255,255,0.08); background: linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(20,20,30,0.8) 100%); position: relative; overflow: hidden;">
|
||||||
|
<div style="position: absolute; top: 0; left: 0; right: 0; height: 3px; background: ${color};"></div>
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
||||||
|
<div style="position: relative; width: 48px; height: 48px; flex-shrink: 0;">
|
||||||
|
<img src="${getAvatarIconSrc(a.avatar_id)}"
|
||||||
|
onerror="handleAvatarImgError(this, ${a.avatar_id})"
|
||||||
|
style="width: 48px; height: 48px; border-radius: 50%; object-fit: cover; border: 2px solid ${color};">
|
||||||
|
${info.element ? `
|
||||||
|
<div style="position: absolute; bottom: -2px; right: -2px; width: 18px; height: 18px; border-radius: 50%; background: #0f172a; display: flex; align-items: center; justify-content: center; border: 1px solid rgba(255,255,255,0.2);">
|
||||||
|
${getElementIconHtml(info.element, 14)}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight: 800; font-size: 1.05rem; color: var(--text-highlight); display: flex; align-items: center; gap: 0.45rem;">
|
||||||
|
<span>${info.name}</span>
|
||||||
|
<span style="font-size: 0.75rem; padding: 0.1rem 0.4rem; border-radius: 4px; background: rgba(255,255,255,0.08); color: var(--text-muted); font-weight: 700;">Lv.${a.level || 80} E${a.rank !== undefined ? a.rank : 0}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.15rem;">
|
||||||
|
${translatePath(info.path)} • ${translateElement(info.element)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<div style="font-size: 1.4rem; font-weight: 900; color: ${color}; letter-spacing: -0.5px;">${pct.toFixed(1)}%</div>
|
||||||
|
<div style="font-size: 0.7rem; color: var(--text-muted); font-weight: 700;">DPS SHARE</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 0.75rem 1rem; border-radius: 8px; background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.05); margin-bottom: 0.85rem; display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 0.725rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;">${t('analystTotalDamage')}</div>
|
||||||
|
<div style="font-size: 1.5rem; font-weight: 900; color: #fff;">${formatNumber(a.total_damage)}</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<div style="font-size: 0.725rem; color: var(--text-muted);">${t('analystMaxHit')}</div>
|
||||||
|
<div style="font-size: 0.95rem; font-weight: 800; color: #f87171;">${formatNumber(maxHit)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="height: 6px; border-radius: 3px; background: rgba(255,255,255,0.06); overflow: hidden; margin-bottom: 1rem;">
|
||||||
|
<div style="height: 100%; width: ${pct}%; background: ${color}; border-radius: 3px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 0.75rem; font-weight: 700; color: var(--text-muted); margin-bottom: 0.4rem;">
|
||||||
|
${t('analystBreakdownTitle')}
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; margin-bottom: 1rem;">
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgNormal')}</span>
|
||||||
|
<span style="font-weight: 700; color: var(--text-main);">${formatNumber(dNormal)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgSkill')}</span>
|
||||||
|
<span style="font-weight: 700; color: #38bdf8;">${formatNumber(dSkill)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgUlt')}</span>
|
||||||
|
<span style="font-weight: 700; color: #c084fc;">${formatNumber(dUlt)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgBreak')}</span>
|
||||||
|
<span style="font-weight: 700; color: #fbbf24;">${formatNumber(dBreak)}</span>
|
||||||
|
</div>
|
||||||
|
${dFollowup > 0 ? `
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgFollowup')}</span>
|
||||||
|
<span style="font-weight: 700; color: #f472b6;">${formatNumber(dFollowup)}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${dDot > 0 ? `
|
||||||
|
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
|
||||||
|
<span style="color: var(--text-muted);">${t('analystDmgDot')}</span>
|
||||||
|
<span style="font-weight: 700; color: #a78bfa;">${formatNumber(dDot)}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap; padding-top: 0.65rem; border-top: 1px solid rgba(255,255,255,0.06); font-size: 0.75rem; color: var(--text-muted);">
|
||||||
|
<div>⏱️ เทิร์น: <strong style="color: #fff;">${a.total_turns || 0}</strong> ${a.extra_turns ? `(+${a.extra_turns})` : ''}</div>
|
||||||
|
<div>🛡️ โล่: <strong style="color: #38bdf8;">${formatNumber(a.total_shield)}</strong></div>
|
||||||
|
<div>💚 ฮีล: <strong style="color: #34d399;">${formatNumber(a.total_heal)}</strong></div>
|
||||||
|
<div>💔 โดนตี: <strong style="color: #f87171;">${formatNumber(a.total_damage_taken)}</strong></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enemies Section
|
||||||
|
if (el.analystEnemiesContainer && el.analystEnemiesCard) {
|
||||||
|
if (report.monsters && report.monsters.length > 0) {
|
||||||
|
el.analystEnemiesCard.style.display = 'block';
|
||||||
|
el.analystEnemiesContainer.innerHTML = report.monsters.map(m => `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0.85rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.05); font-size: 0.825rem;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.6rem;">
|
||||||
|
<span style="font-size: 1.2rem;">👾</span>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight: 700; color: var(--text-highlight);">Monster #${m.monster_id || m.entity_id}</div>
|
||||||
|
<div style="font-size: 0.725rem; color: var(--text-muted);">Wave: ${m.wave || 1} • Entity: ${m.entity_id}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<div style="font-weight: 800; color: #f87171;">${formatNumber(m.total_damage_taken)} DMG Taken</div>
|
||||||
|
<div style="font-size: 0.725rem; color: var(--text-muted);">${m.total_turns || 0} Turns (${formatNumber(m.total_damage)} Dealt)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
el.analystEnemiesCard.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDashboardBattleBanner() {
|
||||||
|
if (!el.dashboardBattleTitle || !el.dashboardBattleSummary) return;
|
||||||
|
const report = analystState.currentReport;
|
||||||
|
if (!report || !report.team_total_damage) {
|
||||||
|
el.dashboardBattleTitle.textContent = "Damage Analyst (จับดาเมจจากในเกม)";
|
||||||
|
el.dashboardBattleSummary.textContent = "ยังไม่มีข้อมูลการต่อสู้ล่าสุด — กดเพื่อเปิดหน้า Damage Analyst";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const topAvatar = (report.avatars && report.avatars.length > 0)
|
||||||
|
? [...report.avatars].sort((a, b) => b.total_damage - a.total_damage)[0]
|
||||||
|
: null;
|
||||||
|
const topName = topAvatar ? getAvatarInfo(topAvatar.avatar_id).name : '';
|
||||||
|
const topDmgPct = topAvatar ? `${topAvatar.damage_percent.toFixed(0)}%` : '';
|
||||||
|
|
||||||
|
el.dashboardBattleTitle.textContent = `💥 ดาเมจรวม ${formatNumber(report.team_total_damage)} (${report.round_cnt} รอบ)`;
|
||||||
|
el.dashboardBattleSummary.textContent = topName
|
||||||
|
? `MVP: ${topName} (${topDmgPct} Share) • ${report.total_battle_turns} เทิร์น • เวลา ${formatDuration(report.cost_time)}`
|
||||||
|
: `สถานะ: ${report.end_status || 'WIN'} • ${report.total_battle_turns} เทิร์น`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportBattleReportJson() {
|
||||||
|
if (!analystState.currentReport) {
|
||||||
|
showToast('ไม่มีข้อมูลการต่อสู้ที่จะส่งออก', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = JSON.stringify(analystState.currentReport, null, 2);
|
||||||
|
const blob = new Blob([text], { type: 'application/json' });
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = `battle-report-${analystState.currentReport.battle_id || Date.now()}.json`;
|
||||||
|
a.click();
|
||||||
|
showToast(`ส่งออก ${a.download} เรียบร้อย`, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearBattleReport() {
|
||||||
|
analystState.currentReport = null;
|
||||||
|
analystState.lastBattleTimestamp = 0;
|
||||||
|
try {
|
||||||
|
await apiRequest('/api/battle_report/reset', 'POST');
|
||||||
|
} catch (_) {}
|
||||||
|
renderEmptyDamageAnalyst();
|
||||||
|
updateDashboardBattleBanner();
|
||||||
|
showToast(t('analystToastCleared'), 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDemoBattleReport() {
|
||||||
|
const demoReport = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
battle_id: 88421,
|
||||||
|
stage_id: 3001,
|
||||||
|
end_status: "WIN",
|
||||||
|
total_battle_turns: 11,
|
||||||
|
total_auto_turns: 0,
|
||||||
|
round_cnt: 0,
|
||||||
|
cost_time: 46.8,
|
||||||
|
challenge_score: 4000,
|
||||||
|
team_total_damage: 2840000,
|
||||||
|
team_total_heal: 8400,
|
||||||
|
team_total_shield: 0,
|
||||||
|
avatars: [
|
||||||
|
{
|
||||||
|
avatar_id: 1310,
|
||||||
|
level: 80,
|
||||||
|
rank: 2,
|
||||||
|
total_damage: 2150000,
|
||||||
|
damage_percent: 75.7,
|
||||||
|
total_damage_taken: 3200,
|
||||||
|
total_heal: 0,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 1890000,
|
||||||
|
total_turns: 5,
|
||||||
|
extra_turns: 2,
|
||||||
|
attack_type_damage: {
|
||||||
|
Normal: 45000,
|
||||||
|
BPSkill: 215000,
|
||||||
|
BreakDamage: 1890000
|
||||||
|
},
|
||||||
|
attack_type_max_damage: {
|
||||||
|
BreakDamage: 620000
|
||||||
|
},
|
||||||
|
skill_times: [
|
||||||
|
{ skill_id: 131001, skill_type: "Normal", skill_use_count: 1 },
|
||||||
|
{ skill_id: 131002, skill_type: "BPSkill", skill_use_count: 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar_id: 8005,
|
||||||
|
level: 80,
|
||||||
|
rank: 6,
|
||||||
|
total_damage: 410000,
|
||||||
|
damage_percent: 14.4,
|
||||||
|
total_damage_taken: 1800,
|
||||||
|
total_heal: 0,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 380000,
|
||||||
|
total_turns: 3,
|
||||||
|
extra_turns: 0,
|
||||||
|
attack_type_damage: {
|
||||||
|
BPSkill: 120000,
|
||||||
|
BreakDamage: 290000
|
||||||
|
},
|
||||||
|
attack_type_max_damage: {
|
||||||
|
BreakDamage: 195000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar_id: 1303,
|
||||||
|
level: 80,
|
||||||
|
rank: 1,
|
||||||
|
total_damage: 160000,
|
||||||
|
damage_percent: 5.6,
|
||||||
|
total_damage_taken: 1400,
|
||||||
|
total_heal: 0,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 160000,
|
||||||
|
total_turns: 2,
|
||||||
|
extra_turns: 0,
|
||||||
|
attack_type_damage: {
|
||||||
|
Normal: 20000,
|
||||||
|
BreakDamage: 140000
|
||||||
|
},
|
||||||
|
attack_type_max_damage: {
|
||||||
|
BreakDamage: 95000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar_id: 1301,
|
||||||
|
level: 80,
|
||||||
|
rank: 6,
|
||||||
|
total_damage: 120000,
|
||||||
|
damage_percent: 4.3,
|
||||||
|
total_damage_taken: 2100,
|
||||||
|
total_heal: 8400,
|
||||||
|
total_shield: 0,
|
||||||
|
total_break_damage: 95000,
|
||||||
|
total_turns: 3,
|
||||||
|
extra_turns: 1,
|
||||||
|
attack_type_damage: {
|
||||||
|
Normal: 25000,
|
||||||
|
Ultra: 35000,
|
||||||
|
BreakDamage: 60000
|
||||||
|
},
|
||||||
|
attack_type_max_damage: {
|
||||||
|
BreakDamage: 60000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
monsters: [
|
||||||
|
{
|
||||||
|
entity_id: 101,
|
||||||
|
monster_id: 3024010,
|
||||||
|
total_turns: 4,
|
||||||
|
total_damage: 8500,
|
||||||
|
total_damage_taken: 2840000,
|
||||||
|
wave: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
analystState.currentReport = demoReport;
|
||||||
|
analystState.lastBattleTimestamp = demoReport.timestamp;
|
||||||
|
renderDamageAnalyst();
|
||||||
|
updateDashboardBattleBanner();
|
||||||
|
showToast("✨ โหลดตัวอย่างการต่อสู้ 0-Cycle เรียบร้อยแล้ว", "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupDamageAnalystEvents() {
|
||||||
|
if (el.btnAnalystFetch) {
|
||||||
|
el.btnAnalystFetch.addEventListener('click', () => fetchLatestBattleReport(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.btnAnalystClear) {
|
||||||
|
el.btnAnalystClear.addEventListener('click', clearBattleReport);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.btnAnalystExport) {
|
||||||
|
el.btnAnalystExport.addEventListener('click', exportBattleReportJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.btnAnalystImport && el.inputAnalystFile) {
|
||||||
|
el.btnAnalystImport.addEventListener('click', () => {
|
||||||
|
el.inputAnalystFile.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.inputAnalystFile.addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (event) => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(event.target.result);
|
||||||
|
let report = null;
|
||||||
|
if (parsed.avatars || parsed.team_total_damage !== undefined) {
|
||||||
|
report = parsed;
|
||||||
|
} else {
|
||||||
|
report = parseFireflySranalysisJson(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report && (report.avatars || report.team_total_damage !== undefined)) {
|
||||||
|
analystState.currentReport = report;
|
||||||
|
analystState.lastBattleTimestamp = report.timestamp || Date.now();
|
||||||
|
renderDamageAnalyst();
|
||||||
|
updateDashboardBattleBanner();
|
||||||
|
showToast(t('analystToastImported'), 'success');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiRequest('/api/battle_report', 'POST', report);
|
||||||
|
} catch (_) {}
|
||||||
|
} else {
|
||||||
|
showToast('รูปแบบไฟล์ JSON ไม่ถูกต้อง (ต้องการ GaC Battle Report หรือ Firefly-Sranalysis JSON)', 'error');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast(`ไฟล์ JSON ผิดพลาด: ${err.message}`, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
e.target.value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.synergyDamageBanner) {
|
||||||
|
el.synergyDamageBanner.addEventListener('click', () => {
|
||||||
|
const btn = document.querySelector('.tab-btn[data-tab="tab-damage-analyst"]');
|
||||||
|
if (btn) btn.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!analystState.autoSyncInterval) {
|
||||||
|
analystState.autoSyncInterval = setInterval(() => {
|
||||||
|
if (el.chkAnalystAutosync && el.chkAnalystAutosync.checked && state.isConnected) {
|
||||||
|
fetchLatestBattleReport(true);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fetchLatestBattleReport = fetchLatestBattleReport;
|
||||||
|
window.loadDemoBattleReport = loadDemoBattleReport;
|
||||||
|
window.exportBattleReportJson = exportBattleReportJson;
|
||||||
|
window.clearBattleReport = clearBattleReport;
|
||||||
|
|
||||||
// Background Ping Loop
|
// Background Ping Loop
|
||||||
function startPingLoop() {
|
function startPingLoop() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -7409,14 +8065,17 @@ async function init() {
|
|||||||
setupTabs();
|
setupTabs();
|
||||||
setupModalSubtabs();
|
setupModalSubtabs();
|
||||||
setupEvents();
|
setupEvents();
|
||||||
|
setupDamageAnalystEvents();
|
||||||
setupGachabaseSyncEvents();
|
setupGachabaseSyncEvents();
|
||||||
setupGMConsoleEvents();
|
setupGMConsoleEvents();
|
||||||
applyTranslations();
|
applyTranslations();
|
||||||
renderLineup(state.statusData?.lineups || DEFAULT_LINEUP);
|
renderLineup(state.statusData?.lineups || DEFAULT_LINEUP);
|
||||||
renderAvatarsGrid();
|
renderAvatarsGrid();
|
||||||
loadEquipment();
|
loadEquipment();
|
||||||
|
renderEmptyDamageAnalyst();
|
||||||
updateGachabaseSyncUI();
|
updateGachabaseSyncUI();
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
|
fetchLatestBattleReport(true);
|
||||||
startPingLoop();
|
startPingLoop();
|
||||||
updateCacheUsageDisplay();
|
updateCacheUsageDisplay();
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-1
@@ -427,9 +427,83 @@ export const TRANSLATIONS = {
|
|||||||
"lcPickerSigTitle": "⭐ กรวยแสงประจำตัวแนะนำ (Signature)",
|
"lcPickerSigTitle": "⭐ กรวยแสงประจำตัวแนะนำ (Signature)",
|
||||||
"lcPickerEquipSig": "⚡ สวมใส่ชิ้นนี้",
|
"lcPickerEquipSig": "⚡ สวมใส่ชิ้นนี้",
|
||||||
"lcPickerUnequip": "✕ ถอดกรวยแสงออก",
|
"lcPickerUnequip": "✕ ถอดกรวยแสงออก",
|
||||||
"lblPingInterval": "ความถี่ในการตรวจสอบเซิร์ฟเวอร์ (Ping Interval)"
|
"lblPingInterval": "ความถี่ในการตรวจสอบเซิร์ฟเวอร์ (Ping Interval)",
|
||||||
|
"tabDamageAnalyst": "💥 Damage Analyst",
|
||||||
|
"analystTitle": "💥 วิเคราะห์ความเสียหายจากการต่อสู้ (Damage Analyst)",
|
||||||
|
"analystSubtitle": "ดักจับและวิเคราะห์ดาเมจจากการต่อสู้จริงในเกม Star Rail (ระบบเดียวกับ FireflyGO / Veritas)",
|
||||||
|
"analystFetchBtn": "🔄 ดึงดาเมจล่าสุดจากในเกม",
|
||||||
|
"analystAutoSync": "⚡ ซิงค์อัตโนมัติเมื่อจบการต่อสู้",
|
||||||
|
"analystImportBtn": "📂 นำเข้าไฟล์ JSON",
|
||||||
|
"analystExportBtn": "💾 ส่งออก JSON",
|
||||||
|
"analystClearBtn": "🗑️ ล้างข้อมูล",
|
||||||
|
"analystNoBattle": "ยังไม่มีข้อมูลการต่อสู้ — กรุณาเริ่มการต่อสู้ในเกม หรือกด 'ดึงดาเมจล่าสุด' / 'นำเข้าไฟล์ JSON'",
|
||||||
|
"analystTotalDamage": "ดาเมจรวมทั้งทีม",
|
||||||
|
"analystCycles": "จำนวนรอบ (Cycles)",
|
||||||
|
"analystTurns": "เทิร์นทั้งหมด",
|
||||||
|
"analystDuration": "ระยะเวลาการต่อสู้",
|
||||||
|
"analystHealing": "การฟื้นฟู HP รวม",
|
||||||
|
"analystShield": "โล่ป้องกันรวม",
|
||||||
|
"analystDpsShare": "สัดส่วนดาเมจ (DPS Share)",
|
||||||
|
"analystBreakdownTitle": "รายละเอียดดาเมจจำแนกตามประเภท",
|
||||||
|
"analystDmgNormal": "🗡️ โจมตีปกติ",
|
||||||
|
"analystDmgSkill": "⚡ สกิลต่อสู้",
|
||||||
|
"analystDmgUlt": "💥 ท่าไม้ตาย",
|
||||||
|
"analystDmgBreak": "🔨 Break / Super Break",
|
||||||
|
"analystDmgFollowup": "🔄 โจมตีต่อเนื่อง",
|
||||||
|
"analystDmgDot": "🩸 ดาเมจต่อเนื่อง (DoT)",
|
||||||
|
"analystMaxHit": "ดาเมจสูงสุดใน 1 ครั้ง",
|
||||||
|
"analystDamageTaken": "ดาเมจที่ได้รับ",
|
||||||
|
"analystTurnsTaken": "เทิร์นแอ็กชัน",
|
||||||
|
"analystSkillUses": "จำนวนครั้งที่ใช้สกิล",
|
||||||
|
"analystEnemyTitle": "👾 รายชื่อศัตรูที่ถูกโจมตี (Targets / Enemies)",
|
||||||
|
"analystEnemyDmgTaken": "ดาเมจที่ศัตรูได้รับ",
|
||||||
|
"analystEnemyTurns": "เทิร์นของศัตรู",
|
||||||
|
"analystStatusWin": "🏆 ชนะการต่อสู้",
|
||||||
|
"analystStatusLose": "💀 พ่ายแพ้",
|
||||||
|
"analystStatusQuit": "🚪 ยอมแพ้ / ออกจากการต่อสู้",
|
||||||
|
"analystToastFetched": "✅ ดึงข้อมูลการต่อสู้ล่าสุดจากเซิร์ฟเวอร์เรียบร้อย",
|
||||||
|
"analystToastImported": "✅ นำเข้าข้อมูลการต่อสู้เรียบร้อย",
|
||||||
|
"analystToastCleared": "🗑️ ล้างข้อมูลการต่อสู้เรียบร้อย",
|
||||||
|
"analystDashboardBanner": "💥 ข้อมูลการต่อสู้ล่าสุด: ดาเมจรวม {dmg} ({cycles} รอบ) — คลิกเพื่อดูบทวิเคราะห์เต็ม →"
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
|
"tabDamageAnalyst": "💥 Damage Analyst",
|
||||||
|
"analystTitle": "💥 In-Game Combat Damage Analyst",
|
||||||
|
"analystSubtitle": "Capture & analyze real combat statistics from Star Rail (FireflyGO / Veritas architecture)",
|
||||||
|
"analystFetchBtn": "🔄 Fetch In-Game Battle",
|
||||||
|
"analystAutoSync": "⚡ Auto-sync on battle finish",
|
||||||
|
"analystImportBtn": "📂 Import Battle JSON",
|
||||||
|
"analystExportBtn": "💾 Export JSON",
|
||||||
|
"analystClearBtn": "🗑️ Clear Report",
|
||||||
|
"analystNoBattle": "No combat data recorded yet — Please play a battle in-game or click 'Fetch In-Game Battle' / 'Import Battle JSON'",
|
||||||
|
"analystTotalDamage": "Team Total Damage",
|
||||||
|
"analystCycles": "Cycles Elapsed",
|
||||||
|
"analystTurns": "Total Turns",
|
||||||
|
"analystDuration": "Combat Duration",
|
||||||
|
"analystHealing": "Total Healing",
|
||||||
|
"analystShield": "Total Shield",
|
||||||
|
"analystDpsShare": "Damage Share (DPS %)",
|
||||||
|
"analystBreakdownTitle": "Damage Breakdown by Category",
|
||||||
|
"analystDmgNormal": "🗡️ Basic ATK",
|
||||||
|
"analystDmgSkill": "⚡ Skill",
|
||||||
|
"analystDmgUlt": "💥 Ultimate",
|
||||||
|
"analystDmgBreak": "🔨 Break / Super Break",
|
||||||
|
"analystDmgFollowup": "🔄 Follow-up",
|
||||||
|
"analystDmgDot": "🩸 DoT",
|
||||||
|
"analystMaxHit": "Max Single Hit",
|
||||||
|
"analystDamageTaken": "Damage Taken",
|
||||||
|
"analystTurnsTaken": "Action Turns",
|
||||||
|
"analystSkillUses": "Ability Usages",
|
||||||
|
"analystEnemyTitle": "👾 Enemy & Target Analysis",
|
||||||
|
"analystEnemyDmgTaken": "Damage Received",
|
||||||
|
"analystEnemyTurns": "Enemy Turns",
|
||||||
|
"analystStatusWin": "🏆 Victory",
|
||||||
|
"analystStatusLose": "💀 Defeat",
|
||||||
|
"analystStatusQuit": "🚪 Retreated / Quitted",
|
||||||
|
"analystToastFetched": "✅ Latest battle statistics fetched successfully",
|
||||||
|
"analystToastImported": "✅ Battle data imported successfully",
|
||||||
|
"analystToastCleared": "🗑️ Battle report cleared",
|
||||||
|
"analystDashboardBanner": "💥 Latest Battle: Total {dmg} ({cycles} Cycles) — View Full Analysis →",
|
||||||
"ahaGlobalDesc": "Enable server-wide Aeon ★ Aha special trait",
|
"ahaGlobalDesc": "Enable server-wide Aeon ★ Aha special trait",
|
||||||
"btnE6S5": "⭐ E6S5",
|
"btnE6S5": "⭐ E6S5",
|
||||||
"btnE0S1": "⚡ E0S1",
|
"btnE0S1": "⚡ E0S1",
|
||||||
|
|||||||
Reference in new issue
Block a user