diff --git a/api/analysis.php b/api/analysis.php index ad48db3..61f86ac 100644 --- a/api/analysis.php +++ b/api/analysis.php @@ -251,6 +251,7 @@ $pdResult = fflogs_gql(<< true]); exit; } if (isset($pdResult['_curl_error'])) { http_response_code(502); echo json_encode(['error' => $pdResult['_curl_error']]); exit; } -// abilityGameID/statusID → display name +// abilityGameID/statusID → display name + damage type $abilityNames = []; +$abilityTypes = []; foreach ($pdResult['data']['reportData']['report']['masterData']['abilities'] ?? [] as $ab) { $abilityNames[(int)$ab['gameID']] = $ab['name']; + if (isset($ab['type'])) $abilityTypes[(int)$ab['gameID']] = (int)$ab['type']; } // gameID → mitigation meta: primary from masterData, statusId fallback for @@ -524,6 +527,7 @@ foreach ($allEvents as $ev) { 'maxHp' => (int)($ev['targetResources']['maxHitPoints'] ?? 0), 'buffs' => $ev['buffs'] ?? '', 'name' => $abilityNames[$abId] ?? $ev['ability']['name'] ?? ('Ability #' . $abId), + 'abilityType' => isset($ev['ability']['type']) ? (int)$ev['ability']['type'] : ($abilityTypes[$abId] ?? null), ]; } @@ -543,6 +547,7 @@ foreach ($byAbility as $abId => $events) { 'timestamp' => (int)$ev['ts'], 'abilityId' => $abId, 'abilityName' => $ev['name'], + 'abilityType' => $ev['abilityType'] ?? null, 'targets' => [], ]; } @@ -633,6 +638,7 @@ foreach ($clusters as $group) { 'timestamp' => $group['timestamp'], 'abilityId' => $group['abilityId'], 'abilityName' => $group['abilityName'], + 'abilityType' => $group['abilityType'] ?? null, 'targets' => $targets, 'totalDamage' => array_sum(array_column($targets, 'amount')), 'isHeavyTankbuster' => $isHeavyTankbuster, diff --git a/api/cache.php b/api/cache.php index 7b18331..b06f825 100644 --- a/api/cache.php +++ b/api/cache.php @@ -2,7 +2,7 @@ declare(strict_types=1); const CACHED_LOG_DIR = __DIR__ . '/../cached_logs'; -const CACHED_LOG_VERSION = 'v8'; +const CACHED_LOG_VERSION = 'v9'; function cache_language(string $language): string { $language = strtolower(trim($language)); diff --git a/css/components.css b/css/components.css index 55ac8a4..fec5cc9 100644 --- a/css/components.css +++ b/css/components.css @@ -143,6 +143,10 @@ select option { background: var(--bg2); } .badge-gold { background: var(--goldbg); border-color: rgba(200,168,75,.4); color: var(--gold); } .badge-planned { opacity: 0.55; border-style: dashed; } +.dmg-type-badge { font-size: 11px; font-weight: 600; padding: 1px 5px; border-radius: 3px; letter-spacing: .03em; } +.dmg-type--phys { background: rgba(230,140,60,.15); color: var(--orange); border: 1px solid rgba(230,140,60,.35); } +.dmg-type--mag { background: rgba(74,158,255,.12); color: var(--blue); border: 1px solid rgba(74,158,255,.3); } + /* ── DR Bar ─────────────────────────────────────────────────────────────────── */ .dr-bar-wrap { background: var(--bg2); diff --git a/css/planner.css b/css/planner.css index 64a1b39..c777bd5 100644 --- a/css/planner.css +++ b/css/planner.css @@ -276,6 +276,9 @@ } .mechanic-name { + display: flex; + align-items: center; + gap: 6px; font-size: 15px; color: var(--t1); font-weight: 500; diff --git a/js/analysis.js b/js/analysis.js index 87fcc93..5b895dc 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -1,5 +1,12 @@ (function () { - const { MITIG_ICONS, JOB_ABBR, ABILITY_JOBS, JOB_ROLE } = window.FF14_DATA; + const { MITIG_ICONS, JOB_ABBR, ABILITY_JOBS, JOB_ROLE, abilityTypeIsPhysical, abilityTypeIsMagical } = window.FF14_DATA; + + function dmgTypeBadge(abilityType) { + if (abilityType == null) return ''; + if (abilityTypeIsPhysical(abilityType)) return 'Phys'; + if (abilityTypeIsMagical(abilityType)) return 'Mag'; + return ''; + } // Deduplicated list of all mitigations across all targets of a ref event function collectRefMitigs(refEvent) { @@ -758,6 +765,7 @@
${ev.abilityName} + ${dmgTypeBadge(ev.abilityType)} — ${fmtDmg(ev.totalDamage)} total ${debuffIconsHtml}
diff --git a/js/planner.js b/js/planner.js index c3f9b53..91cda55 100644 --- a/js/planner.js +++ b/js/planner.js @@ -749,7 +749,7 @@ function renderMechanicListHtml(plan) {
${escHtml(fmtTimestamp(m.timestamp))}
${m.phase ? `
${escHtml(m.phase)}
` : ''} -
${escHtml(m.name)}
+
${escHtml(m.name)}${dmgTypeBadge(m.abilityType)}
${m.unmitigatedDamage ? `
${fmtNumber(m.unmitigatedDamage)} unmitigiert${avgHp ? ` ∅ ${fmtNumber(avgHp)} HP` : ''}
` : '' @@ -2338,7 +2338,16 @@ const { MELEE_JOBS, MITIG_ICONS, TANK_JOBS, + abilityTypeIsPhysical, + abilityTypeIsMagical, } = window.FF14_DATA; + +function dmgTypeBadge(abilityType) { + if (abilityType == null) return ''; + if (abilityTypeIsPhysical(abilityType)) return 'Phys'; + if (abilityTypeIsMagical(abilityType)) return 'Mag'; + return ''; +} // Groups of abilities that are functionally equivalent across different jobs. // Used to suggest replacements when a job is missing from the composition. const ABILITY_EQUIVALENTS = [ @@ -2442,6 +2451,7 @@ function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitiga id: crypto.randomUUID(), name: ev.abilityName, abilityId: ev.abilityId, + abilityType: ev.abilityType ?? null, timestamp: relTs, phase: phase?.name ?? '', unmitigatedDamage: avgUnmit,