diff --git a/api/analysis.php b/api/analysis.php index 7ce143e..4b6da17 100644 --- a/api/analysis.php +++ b/api/analysis.php @@ -56,29 +56,63 @@ function fflogs_gql(string $query): array { return json_decode($body, true) ?? ['_parse_error' => true]; } -// ── Party-wide mitigation + boss debuffs to track ───────────────────────── -// Barriers (dr = 0) are shown without a percentage. +// ── Party-wide mitigation, shields + boss debuffs to track ───────────────── +// buffType 'buff' = damage reduction, shown as icons per player +// buffType 'shield' = barrier, shown only as tooltip on absorbed value +// buffType 'debuff' = boss debuff, shown in event header const MITIGATION_ABILITIES = [ - 'Passage of Arms' => ['dr' => 15, 'buffType' => 'buff'], - 'Divine Veil' => ['dr' => 0, 'buffType' => 'buff'], - 'Shake It Off' => ['dr' => 0, 'buffType' => 'buff'], - 'Dark Missionary' => ['dr' => 10, 'buffType' => 'buff'], - 'Heart of Light' => ['dr' => 10, 'buffType' => 'buff'], - 'Temperance' => ['dr' => 10, 'buffType' => 'buff'], - 'Sacred Soil' => ['dr' => 10, 'buffType' => 'buff'], - 'Expedient' => ['dr' => 10, 'buffType' => 'buff'], - 'Fey Illumination' => ['dr' => 5, 'buffType' => 'buff'], - 'Collective Unconscious' => ['dr' => 10, 'buffType' => 'buff'], - 'Holos' => ['dr' => 10, 'buffType' => 'buff'], - 'Kerachole' => ['dr' => 10, 'buffType' => 'buff'], - 'Panhaima' => ['dr' => 0, 'buffType' => 'buff'], - 'Troubadour' => ['dr' => 15, 'buffType' => 'buff'], - 'Tactician' => ['dr' => 15, 'buffType' => 'buff'], - 'Shield Samba' => ['dr' => 15, 'buffType' => 'buff'], - 'Magick Barrier' => ['dr' => 10, 'buffType' => 'buff'], - 'Reprisal' => ['dr' => 10, 'buffType' => 'debuff'], - 'Feint' => ['dr' => 10, 'buffType' => 'debuff'], - 'Addle' => ['dr' => 10, 'buffType' => 'debuff'], + // ── Damage reduction buffs ────────────────────────────────────────────── + 'Passage of Arms' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001175], + 'Dark Missionary' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1001894], + 'Heart of Light' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1001839], + 'Temperance' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1001873], + 'Sacred Soil' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1001944], + 'Expedient' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1002711], // FFLogs: "Desperate Measures" + 'Fey Illumination' => ['dr' => 5, 'buffType' => 'buff', 'statusId' => 1000317], + 'Collective Unconscious' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1000849], + 'Holos' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1003003], + 'Kerachole' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1002618], + 'Troubadour' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001934], + 'Tactician' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001951], + 'Shield Samba' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001826], + 'Magick Barrier' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1002707], + // ── Shields ───────────────────────────────────────────────────────────── + // PLD + 'Divine Veil' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001362], + 'Guardian' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003830], // FFLogs: "Guardian's Will" + // WAR + 'Shake It Off' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001457], + 'Bloodwhetting' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002678], + // WHM + 'Divine Benison' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001218], + 'Divine Caress' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003903], + // AST + 'Intersection' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001889], + 'Neutral Sect' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001921], + 'the Spire' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003892], // FFLogs: "The Spire" + // SGE + 'Panhaima' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002613], + 'Holosakos' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003365], + 'Eukrasian Prognosis' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002609], + 'Eukrasian Prognosis II' => ['dr' => 0, 'buffType' => 'shield'], // TODO + 'Eukrasian Diagnosis' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002607], + 'Differential Diagnosis' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002608], + 'Haima' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002612], + // SCH + 'Galvanize' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1000297], + 'Seraphic Veil' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001917], + 'Catalyze' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001918], + // SMN + 'Radiant Aegis' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002702], + // PCT + 'Tempera Coat' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003686], + 'Tempera Grassa' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1003687], + // DNC + 'Improvised Finish' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1002697], + // ── Boss debuffs ──────────────────────────────────────────────────────── + 'Reprisal' => ['dr' => 10, 'buffType' => 'debuff', 'statusId' => 1001193], + 'Feint' => ['dr' => 10, 'buffType' => 'debuff', 'statusId' => 1001195], + 'Addle' => ['dr' => 10, 'buffType' => 'debuff', 'statusId' => 1001203], ]; function resolveMitigations(string $buffStr, array $mitigIdMap): array { @@ -127,13 +161,20 @@ foreach ($pdResult['data']['reportData']['report']['masterData']['abilities'] ?? $abilityNames[(int)$ab['gameID']] = $ab['name']; } -// gameID → mitigation meta (only for abilities present in this report) +// gameID → mitigation meta: primary from masterData, statusId fallback for +// abilities whose status ID isn't in masterData (add 'statusId' to the entry +// in MITIGATION_ABILITIES when discovered via the Event Explorer). $mitigIdMap = []; foreach ($abilityNames as $gameId => $name) { if (isset(MITIGATION_ABILITIES[$name])) { $mitigIdMap[$gameId] = array_merge(['name' => $name], MITIGATION_ABILITIES[$name]); } } +foreach (MITIGATION_ABILITIES as $name => $meta) { + if (isset($meta['statusId']) && !isset($mitigIdMap[$meta['statusId']])) { + $mitigIdMap[$meta['statusId']] = array_merge(['name' => $name], $meta); + } +} // player actorID → player data $pdRaw = $pdResult['data']['reportData']['report']['playerDetails'] ?? null; @@ -153,6 +194,7 @@ foreach ($roleMap as $group => $role) { } } + // ── 2. Damage-taken events (paginated) ───────────────────────────────────── $allEvents = []; $nextPage = $startTime; @@ -203,9 +245,11 @@ foreach ($allEvents as $ev) { $byAbility[$abId][] = [ 'ts' => (float)($ev['timestamp'] ?? 0), 'tgtId' => $tgtId, - 'amount' => (int)($ev['amount'] ?? 0), - 'absorbed' => (int)($ev['absorbed'] ?? 0), - 'overkill' => (int)($ev['overkill'] ?? 0), + 'amount' => (int)($ev['amount'] ?? 0), + 'absorbed' => (int)($ev['absorbed'] ?? 0), + 'overkill' => (int)($ev['overkill'] ?? 0), + 'unmitigatedAmount' => (int)($ev['unmitigatedAmount'] ?? 0), + 'mitigated' => (int)($ev['mitigated'] ?? 0), 'hp' => (int)($ev['targetResources']['hitPoints'] ?? 0), 'maxHp' => (int)($ev['targetResources']['maxHitPoints'] ?? 0), 'buffs' => $ev['buffs'] ?? '', @@ -237,16 +281,20 @@ foreach ($byAbility as $abId => $events) { if (!isset($current['targets'][$tgtId])) { $current['targets'][$tgtId] = [ 'id' => $tgtId, - 'amount' => 0, - 'absorbed' => 0, + 'amount' => 0, + 'absorbed' => 0, + 'unmitigatedAmount' => 0, + 'mitigated' => 0, 'overkill' => 0, 'hp' => $ev['hp'], 'maxHp' => $ev['maxHp'], 'buffs' => $ev['buffs'], ]; } - $current['targets'][$tgtId]['amount'] += $ev['amount']; - $current['targets'][$tgtId]['absorbed'] += $ev['absorbed']; + $current['targets'][$tgtId]['amount'] += $ev['amount']; + $current['targets'][$tgtId]['absorbed'] += $ev['absorbed']; + $current['targets'][$tgtId]['unmitigatedAmount'] += $ev['unmitigatedAmount']; + $current['targets'][$tgtId]['mitigated'] += $ev['mitigated']; $current['targets'][$tgtId]['overkill'] += $ev['overkill']; } if ($current !== null) $clusters[] = $current; @@ -264,8 +312,10 @@ foreach ($clusters as $group) { 'name' => $p['name'] ?? '?', 'type' => $p['type'] ?? '', 'role' => $p['role'] ?? 'dps', - 'amount' => $tgt['amount'], - 'absorbed' => $tgt['absorbed'], + 'amount' => $tgt['amount'], + 'absorbed' => $tgt['absorbed'], + 'unmitigatedAmount' => $tgt['unmitigatedAmount'], + 'mitigated' => $tgt['mitigated'], 'overkill' => $tgt['overkill'], 'hp' => $tgt['hp'], 'maxHp' => $tgt['maxHp'], diff --git a/assets/icons/mitigation/bloodwhetting.png b/assets/icons/mitigation/bloodwhetting.png new file mode 100644 index 0000000..d946f14 Binary files /dev/null and b/assets/icons/mitigation/bloodwhetting.png differ diff --git a/assets/icons/mitigation/divine-benison.png b/assets/icons/mitigation/divine-benison.png new file mode 100644 index 0000000..9eecaa1 Binary files /dev/null and b/assets/icons/mitigation/divine-benison.png differ diff --git a/assets/icons/mitigation/divine-caress.png b/assets/icons/mitigation/divine-caress.png new file mode 100644 index 0000000..f5d7132 Binary files /dev/null and b/assets/icons/mitigation/divine-caress.png differ diff --git a/assets/icons/mitigation/eukrasian-diagnosis.png b/assets/icons/mitigation/eukrasian-diagnosis.png new file mode 100644 index 0000000..33c4f65 Binary files /dev/null and b/assets/icons/mitigation/eukrasian-diagnosis.png differ diff --git a/assets/icons/mitigation/eukrasian-prognosis-ii.png b/assets/icons/mitigation/eukrasian-prognosis-ii.png new file mode 100644 index 0000000..fe93500 Binary files /dev/null and b/assets/icons/mitigation/eukrasian-prognosis-ii.png differ diff --git a/assets/icons/mitigation/eukrasian-prognosis.png b/assets/icons/mitigation/eukrasian-prognosis.png new file mode 100644 index 0000000..f17814f Binary files /dev/null and b/assets/icons/mitigation/eukrasian-prognosis.png differ diff --git a/assets/icons/mitigation/galvanize.png b/assets/icons/mitigation/galvanize.png new file mode 100644 index 0000000..2c4d0c0 Binary files /dev/null and b/assets/icons/mitigation/galvanize.png differ diff --git a/assets/icons/mitigation/guardian.png b/assets/icons/mitigation/guardian.png new file mode 100644 index 0000000..b1acdfe Binary files /dev/null and b/assets/icons/mitigation/guardian.png differ diff --git a/assets/icons/mitigation/haima.png b/assets/icons/mitigation/haima.png new file mode 100644 index 0000000..0285241 Binary files /dev/null and b/assets/icons/mitigation/haima.png differ diff --git a/assets/icons/mitigation/improvised-finish.png b/assets/icons/mitigation/improvised-finish.png new file mode 100644 index 0000000..22fcec9 Binary files /dev/null and b/assets/icons/mitigation/improvised-finish.png differ diff --git a/assets/icons/mitigation/intersection.png b/assets/icons/mitigation/intersection.png new file mode 100644 index 0000000..f037e81 Binary files /dev/null and b/assets/icons/mitigation/intersection.png differ diff --git a/assets/icons/mitigation/neutral-sect.png b/assets/icons/mitigation/neutral-sect.png new file mode 100644 index 0000000..8f4aa0c Binary files /dev/null and b/assets/icons/mitigation/neutral-sect.png differ diff --git a/assets/icons/mitigation/radiant-aegis.png b/assets/icons/mitigation/radiant-aegis.png new file mode 100644 index 0000000..81c3071 Binary files /dev/null and b/assets/icons/mitigation/radiant-aegis.png differ diff --git a/assets/icons/mitigation/seraphic-veil.png b/assets/icons/mitigation/seraphic-veil.png new file mode 100644 index 0000000..d0e757e Binary files /dev/null and b/assets/icons/mitigation/seraphic-veil.png differ diff --git a/assets/icons/mitigation/tempera-coat.png b/assets/icons/mitigation/tempera-coat.png new file mode 100644 index 0000000..dfa65a8 Binary files /dev/null and b/assets/icons/mitigation/tempera-coat.png differ diff --git a/assets/icons/mitigation/tempera-grassa.png b/assets/icons/mitigation/tempera-grassa.png new file mode 100644 index 0000000..29ab8e6 Binary files /dev/null and b/assets/icons/mitigation/tempera-grassa.png differ diff --git a/assets/icons/mitigation/the-spire.png b/assets/icons/mitigation/the-spire.png new file mode 100644 index 0000000..569542c Binary files /dev/null and b/assets/icons/mitigation/the-spire.png differ diff --git a/js/analysis.js b/js/analysis.js index 0b75574..d5244f1 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -1,8 +1,7 @@ (function () { const MITIG_ICONS = { + // DR buffs 'Passage of Arms': 'assets/icons/mitigation/passage-of-arms.png', - 'Divine Veil': 'assets/icons/mitigation/divine-veil.png', - 'Shake It Off': 'assets/icons/mitigation/shake-it-off.png', 'Dark Missionary': 'assets/icons/mitigation/dark-missionary.png', 'Heart of Light': 'assets/icons/mitigation/heart-of-light.png', 'Temperance': 'assets/icons/mitigation/temperance.png', @@ -12,14 +11,37 @@ 'Collective Unconscious': 'assets/icons/mitigation/collective-unconscious.png', 'Holos': 'assets/icons/mitigation/holos.png', 'Kerachole': 'assets/icons/mitigation/kerachole.png', - 'Panhaima': 'assets/icons/mitigation/panhaima.png', 'Troubadour': 'assets/icons/mitigation/troubadour.png', 'Tactician': 'assets/icons/mitigation/tactician.png', 'Shield Samba': 'assets/icons/mitigation/shield-samba.png', 'Magick Barrier': 'assets/icons/mitigation/magick-barrier.png', + // Debuffs 'Reprisal': 'assets/icons/mitigation/reprisal.png', 'Feint': 'assets/icons/mitigation/feint.png', 'Addle': 'assets/icons/mitigation/addle.png', + // Shields + 'Divine Veil': 'assets/icons/mitigation/divine-veil.png', + 'Guardian': 'assets/icons/mitigation/guardian.png', + 'Shake It Off': 'assets/icons/mitigation/shake-it-off.png', + 'Bloodwhetting': 'assets/icons/mitigation/bloodwhetting.png', + 'Divine Benison': 'assets/icons/mitigation/divine-benison.png', + 'Divine Caress': 'assets/icons/mitigation/divine-caress.png', + 'Intersection': 'assets/icons/mitigation/intersection.png', + 'Neutral Sect': 'assets/icons/mitigation/neutral-sect.png', + 'the Spire': 'assets/icons/mitigation/the-spire.png', + 'Panhaima': 'assets/icons/mitigation/panhaima.png', + 'Holosakos': 'assets/icons/mitigation/holos.png', + 'Eukrasian Prognosis': 'assets/icons/mitigation/eukrasian-prognosis.png', + 'Eukrasian Prognosis II': 'assets/icons/mitigation/eukrasian-prognosis-ii.png', + 'Eukrasian Diagnosis': 'assets/icons/mitigation/eukrasian-diagnosis.png', + 'Differential Diagnosis': 'assets/icons/mitigation/eukrasian-diagnosis.png', + 'Haima': 'assets/icons/mitigation/haima.png', + 'Galvanize': 'assets/icons/mitigation/galvanize.png', + 'Seraphic Veil': 'assets/icons/mitigation/seraphic-veil.png', + 'Radiant Aegis': 'assets/icons/mitigation/radiant-aegis.png', + 'Tempera Coat': 'assets/icons/mitigation/tempera-coat.png', + 'Tempera Grassa': 'assets/icons/mitigation/tempera-grassa.png', + 'Improvised Finish': 'assets/icons/mitigation/improvised-finish.png', }; const JOB_ABBR = { @@ -339,10 +361,15 @@ // Current targets const targets = visibleTargets.map(t => { const hpBar = (t.maxHp > 0) ? (() => { - const afterPct = t.hp / t.maxHp * 100; - const damagePct = t.amount / t.maxHp * 100; - const hpColor = afterPct > 50 ? 'var(--green)' : afterPct > 25 ? '#e8a020' : 'var(--red)'; - return `
+ const afterPct = t.hp / t.maxHp * 100; + const damagePct = t.amount / t.maxHp * 100; + const hpColor = afterPct > 50 ? 'var(--green)' : afterPct > 25 ? '#e8a020' : 'var(--red)'; + const missingBefore = Math.max(0, t.maxHp - t.hp - t.amount); + const fmt = n => n.toLocaleString(); + const hpPct = (t.hp / t.maxHp * 100).toFixed(1); + const missingPct = (missingBefore / t.maxHp * 100).toFixed(1); + const tooltip = `MaxHP: ${fmt(t.maxHp)}\nCurrentHP: ${fmt(t.hp)}\nHP-%: ${hpPct}%\nMissingBefore: ${fmt(missingBefore)}\nMissing-%: ${missingPct}%`; + return `
`; @@ -351,10 +378,11 @@ const currentMitigNames = new Set((t.mitigations ?? []).map(m => m.name)); const refTarget = refEv?.targets?.find(rt => rt.name === t.name); const missingMitigs = refTarget - ? (refTarget.mitigations ?? []).filter(m => m.buffType !== 'debuff' && !currentMitigNames.has(m.name)) + ? (refTarget.mitigations ?? []).filter(m => m.buffType === 'buff' && !currentMitigNames.has(m.name)) : []; - const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType !== 'debuff').map(m => { + // DR buff icons (shown below player box) + const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => { const iconSrc = MITIG_ICONS[m.name]; if (!iconSrc) return ''; const dr = m.dr > 0 ? ` −${m.dr}%` : ''; @@ -367,6 +395,17 @@ return `${m.name}`; }).join(''); + // Shield tooltip on absorbed value + const activeShields = (t.mitigations ?? []).filter(m => m.buffType === 'shield'); + const missingShields = refTarget + ? (refTarget.mitigations ?? []).filter(m => m.buffType === 'shield' && !currentMitigNames.has(m.name)) + : []; + const shieldLines = [ + ...activeShields.map(s => s.name), + ...missingShields.map(s => `[fehlt: ${s.name}]`), + ]; + const shieldTitle = shieldLines.length ? shieldLines.join('\n') : null; + const dead = t.hp === 0 && t.maxHp > 0; return ` @@ -379,7 +418,7 @@
${t.name} - ${fmtDmg(t.amount)}${t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''} + ${fmtDmg(t.amount)}${t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''}
${hpBar}
@@ -418,7 +457,7 @@ ? `${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}` : ''; - const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType !== 'debuff').map(m => { + const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => { const iconSrc = MITIG_ICONS[m.name]; if (!iconSrc) return ''; const dr = m.dr > 0 ? ` −${m.dr}%` : ''; diff --git a/js/app.js b/js/app.js index 56963ad..5dde654 100644 --- a/js/app.js +++ b/js/app.js @@ -14,6 +14,12 @@ document.addEventListener('DOMContentLoaded', () => { let allFights = []; + const codeInput = form.elements['report_code']; + codeInput.addEventListener('input', () => { + const match = codeInput.value.match(/fflogs\.com\/reports\/([A-Za-z0-9]+)/); + if (match) codeInput.value = match[1]; + }); + function formatDuration(ms) { const min = Math.floor(ms / 60000); const sec = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0'); diff --git a/templates/report-form.php b/templates/report-form.php index e6a9711..abb662c 100644 --- a/templates/report-form.php +++ b/templates/report-form.php @@ -7,7 +7,7 @@