From 565dedc56863b2b0530dc15931c790aea1487e27 Mon Sep 17 00:00:00 2001 From: xziino Date: Fri, 22 May 2026 22:53:01 +0200 Subject: [PATCH] Analyse-Tab: Job-basierter Mitigation-Vergleich statt Namens-Match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref-Vergleich prüft jetzt ob die aktuelle Gruppe den passenden Job hat (via ABILITY_JOBS-Map), statt Spielernamen zu matchen. Fehlende Mitigations werden nur noch in der REF-Zeile hervorgehoben — der aktive Pull zeigt ausschließlich tatsächlich genutzte Mitigations. Co-Authored-By: Claude Sonnet 4.6 --- js/analysis.js | 109 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/js/analysis.js b/js/analysis.js index 96d262a..6fdede0 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -54,6 +54,63 @@ 'Pictomancer': 'PCT', 'BlueMage': 'BLU', }; + // ability name → jobs that can provide it (for job-based ref comparison) + const ABILITY_JOBS = { + 'Passage of Arms': ['PLD'], + 'Divine Veil': ['PLD'], + 'Guardian': ['PLD'], + 'Reprisal': ['PLD', 'WAR', 'DRK', 'GNB'], + 'Shake It Off': ['WAR'], + 'Bloodwhetting': ['WAR'], + 'Dark Missionary': ['DRK'], + 'Heart of Light': ['GNB'], + 'Temperance': ['WHM'], + 'Divine Benison': ['WHM'], + 'Divine Caress': ['WHM'], + 'Sacred Soil': ['SCH'], + 'Expedient': ['SCH'], + 'Fey Illumination': ['SCH'], + 'Galvanize': ['SCH'], + 'Seraphic Veil': ['SCH'], + 'Catalyze': ['SCH'], + 'Collective Unconscious': ['AST'], + 'Neutral Sect': ['AST'], + 'Intersection': ['AST'], + 'the Spire': ['AST'], + 'Kerachole': ['SGE'], + 'Holos': ['SGE'], + 'Holosakos': ['SGE'], + 'Panhaima': ['SGE'], + 'Eukrasian Prognosis': ['SGE'], + 'Eukrasian Prognosis II': ['SGE'], + 'Eukrasian Diagnosis': ['SGE'], + 'Differential Diagnosis': ['SGE'], + 'Haima': ['SGE'], + 'Troubadour': ['BRD'], + 'Tactician': ['MCH'], + 'Shield Samba': ['DNC'], + 'Improvised Finish': ['DNC'], + 'Feint': ['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR'], + 'Addle': ['SCH', 'SGE', 'BLM', 'SMN', 'RDM', 'PCT'], + 'Radiant Aegis': ['SMN'], + 'Magick Barrier': ['RDM'], + 'Tempera Coat': ['PCT'], + 'Tempera Grassa': ['PCT'], + }; + + // Deduplicated list of all mitigations across all targets of a ref event + function collectRefMitigs(refEvent) { + if (!refEvent) return []; + const seen = new Set(), result = []; + for (const t of refEvent.targets ?? []) { + for (const m of (t.mitigations ?? [])) { + const k = m.key ?? m.name; + if (!seen.has(k)) { seen.add(k); result.push(m); } + } + } + return result; + } + function abbr(type) { return JOB_ABBR[type] ?? type.slice(0, 3).toUpperCase(); } @@ -441,6 +498,8 @@ return; } + const currentFightJobSet = new Set(currentPlayers.map(p => JOB_ABBR[p.type]).filter(Boolean)); + // Build reference index: abilityName → [events in order] const refIndex = {}; for (const ev of refEvents) { @@ -455,6 +514,11 @@ const occ = abilityOccurrence[ev.abilityName] ?? 0; abilityOccurrence[ev.abilityName] = occ + 1; const refEv = refEvents.length ? (refIndex[ev.abilityName]?.[occ] ?? null) : null; + const allRefMitigs = collectRefMitigs(refEv); + const currentEventMitigKeys = new Set(); + for (const t of ev.targets) { + for (const m of (t.mitigations ?? [])) currentEventMitigKeys.add(m.key ?? m.name); + } const visibleTargets = ev.targets.filter(t => !hiddenPlayers.has(t.id) && @@ -476,7 +540,11 @@ } } const eventMissingDebuffs = refEv - ? (refEv.targets[0]?.mitigations ?? []).filter(m => m.buffType === 'debuff' && !seenDebuffKeys.has(m.key ?? m.name)) + ? allRefMitigs.filter(m => { + if (m.buffType !== 'debuff' || seenDebuffKeys.has(m.key ?? m.name)) return false; + const jobs = ABILITY_JOBS[m.key] ?? ABILITY_JOBS[m.name]; + return jobs ? jobs.some(j => currentFightJobSet.has(j)) : false; + }) : []; const debuffIconsHtml = [ ...eventDebuffs.map(m => ({ ...m, missing: false })), @@ -507,12 +575,6 @@ `; })() : ''; - const currentMitigKeys = new Set((t.mitigations ?? []).map(m => m.key ?? m.name)); - const refTarget = refEv?.targets?.find(rt => t.type ? rt.type === t.type : rt.name === t.name); - const missingMitigs = refTarget - ? (refTarget.mitigations ?? []).filter(m => m.buffType === 'buff' && !currentMitigKeys.has(m.key ?? m.name)) - : []; - // DR buff icons (shown below player box) const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => { const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name]; @@ -522,14 +584,8 @@ }).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' && !currentMitigKeys.has(m.key ?? m.name)) - : []; - const shieldLines = [ - ...activeShields.map(s => s.name), - ...missingShields.map(s => `[fehlt: ${s.name}]`), - ]; + const activeShields = (t.mitigations ?? []).filter(m => m.buffType === 'shield'); + const shieldLines = activeShields.map(s => s.name); const shieldTitle = shieldLines.length ? shieldLines.join('\n') : null; const dead = t.hp === 0 && t.maxHp > 0; @@ -561,8 +617,8 @@ (!playerFilter || t.name.toLowerCase().includes(playerFilter)) ); if (refVisible.length) { - const currentByType = {}; - ev.targets.forEach(t => { currentByType[t.type || t.name] = t; }); + const currentByName = {}; + ev.targets.forEach(t => { currentByName[t.name] = t; }); const seenRefDebuffKeys = new Set(); const refDebuffIconsHtml = refVisible.flatMap(t => (t.mitigations ?? [])) @@ -575,7 +631,7 @@ }).join(''); const refCards = refVisible.map(t => { - const curr = currentByType[t.type || t.name]; + const curr = currentByName[t.name]; const diff = curr ? curr.amount - t.amount : 0; const dead = t.hp === 0 && t.maxHp > 0; @@ -583,13 +639,14 @@ ? `${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}` : ''; - const currMitigKeys = new Set((curr?.mitigations ?? []).map(m => m.key ?? m.name)); - const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => { const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name]; if (!iconSrc) return ''; - const dr = m.dr > 0 ? ` −${m.dr}%` : ''; - const missing = !currMitigKeys.has(m.key ?? m.name); + const dr = m.dr > 0 ? ` −${m.dr}%` : ''; + const k = m.key ?? m.name; + const jobs = ABILITY_JOBS[k] ?? ABILITY_JOBS[m.name]; + const currentGroupHasJob = jobs ? jobs.some(j => currentFightJobSet.has(j)) : false; + const missing = currentGroupHasJob && !currentEventMitigKeys.has(k); const cls = missing ? ' aoe-buff-ref-unique' : ''; const titleSufx = missing ? ' (fehlt im aktuellen Pull)' : ''; return `${m.name}`; @@ -597,7 +654,13 @@ const refShields = (t.mitigations ?? []).filter(m => m.buffType === 'shield'); const refShieldTitle = refShields.length - ? refShields.map(s => currMitigKeys.has(s.key ?? s.name) ? s.name : `${s.name} [fehlt im aktuellen Pull]`).join('\n') + ? refShields.map(s => { + const k = s.key ?? s.name; + const jobs = ABILITY_JOBS[k]; + const currentGroupHasJob = jobs ? jobs.some(j => currentFightJobSet.has(j)) : false; + const isMissing = currentGroupHasJob && !currentEventMitigKeys.has(k); + return isMissing ? `${s.name} [fehlt im aktuellen Pull]` : s.name; + }).join('\n') : null; return `