(function () { const MITIG_ICONS = { // DR buffs 'Passage of Arms': 'assets/icons/mitigation/passage-of-arms.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', 'Sacred Soil': 'assets/icons/mitigation/sacred-soil.png', 'Expedient': 'assets/icons/mitigation/expedient.png', 'Fey Illumination': 'assets/icons/mitigation/fey-illumination.png', 'Collective Unconscious': 'assets/icons/mitigation/collective-unconscious.png', 'Holos': 'assets/icons/mitigation/holos.png', 'Kerachole': 'assets/icons/mitigation/kerachole.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 = { 'Paladin': 'PLD', 'Warrior': 'WAR', 'DarkKnight': 'DRK', 'Gunbreaker': 'GNB', 'WhiteMage': 'WHM', 'Scholar': 'SCH', 'Astrologian': 'AST', 'Sage': 'SGE', 'Monk': 'MNK', 'Dragoon': 'DRG', 'Ninja': 'NIN', 'Samurai': 'SAM', 'Reaper': 'RPR', 'Viper': 'VPR', 'Bard': 'BRD', 'Machinist': 'MCH', 'Dancer': 'DNC', 'BlackMage': 'BLM', 'Summoner': 'SMN', 'RedMage': 'RDM', 'Pictomancer': 'PCT', 'BlueMage': 'BLU', }; function abbr(type) { return JOB_ABBR[type] ?? type.slice(0, 3).toUpperCase(); } function fmtTime(ms, start) { const rel = ms - start; const min = Math.floor(rel / 60000); const sec = String(Math.floor((rel % 60000) / 1000)).padStart(2, '0'); return `${min}:${sec}`; } function fmtDmg(n) { if (n >= 1_000_000) return (n / 1_000_000).toFixed(2) + 'M'; if (n >= 1_000) return Math.round(n / 1_000) + 'k'; return String(n); } function fmtDur(ms) { const min = Math.floor(ms / 60000); const sec = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0'); return `${min}:${sec}`; } function normalizeFightName(name) { return String(name ?? '').trim().toLowerCase(); } function currentFightName() { const fight = (window.App?.fights ?? []).find(f => f.id === window.App?.fightId); return normalizeFightName(fight?.name); } function isSameFightName(fight) { const name = currentFightName(); return name !== '' && normalizeFightName(fight?.name) === name; } let hiddenPlayers = new Set(); let hiddenPlayerNames = new Set(); let lastEvents = []; let lastFightStart = 0; let playerFilter = ''; let phaseFilter = { startTime: 0, endTime: Infinity }; let refEvents = []; let refFightStart = 0; let refPlayers = []; let currentPlayers = []; let extFights = []; let extReportCode = ''; let mitigationNames = {}; // ── Player grid ────────────────────────────────────────────────────────── function renderPlayers(players) { currentPlayers = players; const grid = document.getElementById('player-grid'); const order = { healer: 0, dps: 1, tank: 2 }; players.sort((a, b) => { const roleCmp = (order[a.role] ?? 2) - (order[b.role] ?? 2); return roleCmp !== 0 ? roleCmp : a.name.localeCompare(b.name); }); hiddenPlayers = new Set(players.filter(p => p.role === 'tank').map(p => p.id)); hiddenPlayerNames = new Set(players.filter(p => p.role === 'tank').map(p => p.name)); grid.innerHTML = players.map(p => `
${abbr(p.type)}
${p.name}
${p.type}
`).join(''); } function renderRefPlayers() { const section = document.getElementById('ref-player-section'); const grid = document.getElementById('ref-player-grid'); if (!refPlayers.length) { section.style.display = 'none'; return; } const currentNames = new Set(currentPlayers.map(p => p.name)); if (!refPlayers.some(p => !currentNames.has(p.name))) { section.style.display = 'none'; return; } const order = { healer: 0, dps: 1, tank: 2 }; const sorted = [...refPlayers].sort((a, b) => { const roleCmp = (order[a.role] ?? 2) - (order[b.role] ?? 2); return roleCmp !== 0 ? roleCmp : a.name.localeCompare(b.name); }); sorted.filter(p => p.role === 'tank').forEach(p => hiddenPlayerNames.add(p.name)); grid.innerHTML = sorted.map(p => `
${abbr(p.type)}
${p.name}
${p.type}
`).join(''); section.style.display = ''; } document.getElementById('ref-player-grid').addEventListener('click', e => { const card = e.target.closest('.player-card'); if (!card) return; const name = card.dataset.playerName; if (hiddenPlayerNames.has(name)) { hiddenPlayerNames.delete(name); card.classList.remove('player-hidden'); } else { hiddenPlayerNames.add(name); card.classList.add('player-hidden'); } renderTimeline(lastEvents, lastFightStart); }); document.getElementById('player-grid').addEventListener('click', e => { const card = e.target.closest('.player-card'); if (!card) return; const id = parseInt(card.dataset.playerId, 10); const name = card.dataset.playerName; if (hiddenPlayers.has(id)) { hiddenPlayers.delete(id); hiddenPlayerNames.delete(name); card.classList.remove('player-hidden'); } else { hiddenPlayers.add(id); hiddenPlayerNames.add(name); card.classList.add('player-hidden'); } renderTimeline(lastEvents, lastFightStart); }); document.getElementById('player-filter').addEventListener('input', e => { playerFilter = e.target.value.trim().toLowerCase(); renderTimeline(lastEvents, lastFightStart); }); // ── Phase select ───────────────────────────────────────────────────────── const phaseSelect = document.getElementById('phase-select'); phaseSelect.addEventListener('change', () => { const phases = window.App?.phases ?? []; const phase = phases.find(p => p.id === parseInt(phaseSelect.value, 10)); if (phase) { phaseFilter = { startTime: phase.startTime, endTime: phase.endTime }; renderTimeline(lastEvents, lastFightStart); } }); function setupPhases(phases) { if (!phases.length) { phaseSelect.style.display = 'none'; phaseFilter = { startTime: 0, endTime: Infinity }; return; } phaseSelect.innerHTML = phases.map(p => `` ).join(''); phaseSelect.value = 0; phaseFilter = { startTime: phases[0].startTime, endTime: phases[0].endTime }; phaseSelect.style.display = ''; } // ── Reference fight select ──────────────────────────────────────────────── const refFightSelect = document.getElementById('ref-fight-select'); refFightSelect.addEventListener('change', async () => { const refId = parseInt(refFightSelect.value, 10); if (!refId) { refEvents = []; refFightStart = 0; refPlayers = []; window.App.setUrlState?.({ compareReportCode: '', compareFightId: '' }); renderRefPlayers(); renderTimeline(lastEvents, lastFightStart); return; } const fight = (window.App?.fights ?? []).find(f => f.id === refId); if (!fight) return; // Clear ext-report selection refExtFightSelect.value = ''; refFightSelect.disabled = true; try { const res = await fetch('api/analysis.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ report_code: window.App.reportCode, fight_id: refId, start_time: fight.startTime, end_time: fight.endTime, language: window.App.language, }), }); const json = await res.json(); if (!json.error && !json.reauth) { refEvents = json.aoe_events ?? []; refFightStart = json.fight_start ?? fight.startTime; refPlayers = []; window.App.setUrlState?.({ compareReportCode: '', compareFightId: refId, language: window.App.language, }); } } catch { } refFightSelect.disabled = false; renderRefPlayers(); renderTimeline(lastEvents, lastFightStart); }); let allSameReportFights = []; function populateRefFightSelect() { const visible = allSameReportFights.filter(f => f.id !== window.App.fightId && isSameFightName(f)); refFightSelect.innerHTML = ''; visible.forEach(f => { const hp = f.kill ? 'Kill' : (f.fightPercentage != null ? f.fightPercentage.toFixed(2) + '%' : '?'); const opt = document.createElement('option'); opt.value = f.id; opt.textContent = `${f.name} — ${fmtDur(f.endTime - f.startTime)} — ${hp}`; refFightSelect.appendChild(opt); }); refFightSelect.style.display = visible.length ? '' : 'none'; } function onFightsLoaded(fights) { allSameReportFights = fights; populateRefFightSelect(); } // ── External report comparison ──────────────────────────────────────────── const refExtToggle = document.getElementById('ref-ext-toggle'); const refExtPanel = document.getElementById('ref-ext-panel'); const refReportInput = document.getElementById('ref-report-input'); const refReportLoad = document.getElementById('ref-report-load'); const refFflogsLink = document.getElementById('ref-fflogs-report-link'); const refExtFightSelect = document.getElementById('ref-ext-fight-select'); function updateRefFflogsLink(fightId = 0) { if (!extReportCode) { refFflogsLink.style.display = 'none'; refFflogsLink.href = '#'; return; } refFflogsLink.href = window.App?.fflogsReportUrl ? window.App.fflogsReportUrl(extReportCode, fightId) : `https://www.fflogs.com/reports/${encodeURIComponent(extReportCode)}${fightId ? `#fight=${fightId}` : ''}`; refFflogsLink.style.display = ''; } refReportInput.addEventListener('input', () => { const match = refReportInput.value.match(/fflogs\.com\/reports\/([A-Za-z0-9]+)/); if (match) refReportInput.value = match[1]; }); refExtToggle.addEventListener('click', () => { const hidden = refExtPanel.style.display === 'none'; refExtPanel.style.display = hidden ? '' : 'none'; }); async function loadExternalReport(code, preferredFightId = 0) { if (!code) return; refReportLoad.disabled = true; refReportLoad.textContent = 'Lädt…'; try { const res = await fetch('api/fight.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ report_code: code, language: window.App.language }), }); const json = await res.json(); if (json.reauth) { window.location.href = window.App?.authStartUrl?.() ?? 'auth/start.php'; return; } const fights = json?.data?.reportData?.report?.fights ?? []; extFights = fights; extReportCode = code; updateRefFflogsLink(); const visibleExt = fights.filter(isSameFightName); refExtFightSelect.innerHTML = ''; visibleExt.forEach(f => { const hp = f.kill ? 'Kill' : (f.fightPercentage != null ? f.fightPercentage.toFixed(2) + '%' : '?'); const opt = document.createElement('option'); opt.value = f.id; opt.textContent = `${f.name} — ${fmtDur(f.endTime - f.startTime)} — ${hp}`; refExtFightSelect.appendChild(opt); }); refExtFightSelect.style.display = visibleExt.length ? '' : 'none'; refExtPanel.style.display = ''; if (preferredFightId && visibleExt.some(f => f.id === preferredFightId)) { refExtFightSelect.value = String(preferredFightId); updateRefFflogsLink(preferredFightId); await loadExternalCompare(preferredFightId); } } catch { } refReportLoad.disabled = false; refReportLoad.textContent = 'Laden'; } refReportLoad.addEventListener('click', async () => { await loadExternalReport(refReportInput.value.trim()); }); async function loadExternalCompare(refId) { if (!refId) { refEvents = []; refFightStart = 0; refPlayers = []; window.App.setUrlState?.({ compareReportCode: '', compareFightId: '' }); renderRefPlayers(); renderTimeline(lastEvents, lastFightStart); return; } const fight = extFights.find(f => f.id === refId); if (!fight) return; // Clear same-report selection refFightSelect.value = ''; refExtFightSelect.disabled = true; try { const res = await fetch('api/analysis.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ report_code: extReportCode, fight_id: refId, start_time: fight.startTime, end_time: fight.endTime, language: window.App.language, }), }); const json = await res.json(); if (!json.error && !json.reauth) { refEvents = json.aoe_events ?? []; refFightStart = json.fight_start ?? fight.startTime; refPlayers = json.players ?? []; window.App.setUrlState?.({ compareReportCode: extReportCode, compareFightId: refId, language: window.App.language, }); } } catch { } refExtFightSelect.disabled = false; renderRefPlayers(); renderTimeline(lastEvents, lastFightStart); } refExtFightSelect.addEventListener('change', async () => { const refId = parseInt(refExtFightSelect.value, 10) || 0; updateRefFflogsLink(refId); await loadExternalCompare(refId); }); // ── Timeline rendering ──────────────────────────────────────────────────── function renderTimeline(events, fightStart) { lastEvents = events; lastFightStart = fightStart; const el = document.getElementById('aoe-timeline'); if (!events.length) { el.innerHTML = '

Keine AoE-Events gefunden

'; return; } // Build reference index: abilityName → [events in order] const refIndex = {}; for (const ev of refEvents) { (refIndex[ev.abilityName] = refIndex[ev.abilityName] ?? []).push(ev); } const abilityOccurrence = {}; const rows = events.map(ev => { if (ev.timestamp < phaseFilter.startTime || ev.timestamp >= phaseFilter.endTime) return ''; // Track occurrence for ref matching const occ = abilityOccurrence[ev.abilityName] ?? 0; abilityOccurrence[ev.abilityName] = occ + 1; const refEv = refEvents.length ? (refIndex[ev.abilityName]?.[occ] ?? null) : null; const visibleTargets = ev.targets.filter(t => !hiddenPlayers.has(t.id) && (!playerFilter || t.name.toLowerCase().includes(playerFilter)) ); if (ev.isHeavyTankbuster && !visibleTargets.some(t => t.role === 'tank')) return ''; if (!visibleTargets.length) return ''; // Collect boss debuffs (Reprisal/Feint/Addle) once at event level const seenDebuffKeys = new Set(); const eventDebuffs = []; for (const t of visibleTargets) { for (const m of (t.mitigations ?? [])) { const key = m.key ?? m.name; if (m.buffType === 'debuff' && !seenDebuffKeys.has(key)) { seenDebuffKeys.add(key); eventDebuffs.push(m); } } } const eventMissingDebuffs = refEv ? (refEv.targets[0]?.mitigations ?? []).filter(m => m.buffType === 'debuff' && !seenDebuffKeys.has(m.key ?? m.name)) : []; const debuffIconsHtml = [ ...eventDebuffs.map(m => ({ ...m, missing: false })), ...eventMissingDebuffs.map(m => ({ ...m, missing: true })), ].map(m => { const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name]; if (!iconSrc) return ''; const dr = m.dr > 0 ? ` −${m.dr}%` : ''; return m.missing ? `${m.name}` : `${m.name}`; }).join(''); // 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)'; 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 `
`; })() : ''; const currentMitigKeys = new Set((t.mitigations ?? []).map(m => m.key ?? m.name)); const refTarget = refEv?.targets?.find(rt => 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]; if (!iconSrc) return ''; const dr = m.dr > 0 ? ` −${m.dr}%` : ''; 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' && !currentMitigKeys.has(m.key ?? 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 `
${abbr(t.type)} ${dead && t.overkill > 0 ? `-${fmtDmg(t.overkill)}` : ''}
${t.name} ${fmtDmg(t.amount)}${t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''}
${hpBar}
${mitigIcons ? `
${mitigIcons}
` : ''}
`; }).join(''); // Reference row let refHtml = ''; if (refEv) { const refVisible = refEv.targets.filter(t => !hiddenPlayerNames.has(t.name) && (!playerFilter || t.name.toLowerCase().includes(playerFilter)) ); if (refVisible.length) { const currentByName = {}; ev.targets.forEach(t => { currentByName[t.name] = t; }); const seenRefDebuffKeys = new Set(); const refDebuffIconsHtml = refVisible.flatMap(t => (t.mitigations ?? [])) .filter(m => m.buffType === 'debuff' && !seenRefDebuffKeys.has(m.key ?? m.name) && seenRefDebuffKeys.add(m.key ?? m.name)) .map(m => { const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name]; if (!iconSrc) return ''; const dr = m.dr > 0 ? ` −${m.dr}%` : ''; return `${m.name}`; }).join(''); const refCards = refVisible.map(t => { const curr = currentByName[t.name]; const diff = curr ? curr.amount - t.amount : 0; const dead = t.hp === 0 && t.maxHp > 0; const deltaHtml = diff !== 0 ? `${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 cls = missing ? ' aoe-buff-ref-unique' : ''; const titleSufx = missing ? ' (fehlt im aktuellen Pull)' : ''; return `${m.name}`; }).join(''); 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') : null; return `
${abbr(t.type)} ${deltaHtml}
${t.name} ${fmtDmg(t.amount)}${t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''}
${refMitigIcons ? `
${refMitigIcons}
` : ''}
`; }).join(''); const totalDiff = ev.totalDamage - refEv.totalDamage; const totalDelta = totalDiff !== 0 ? `${totalDiff > 0 ? '+' : ''}${fmtDmg(totalDiff)}` : ''; refHtml = `
REF ${fmtDmg(refEv.totalDamage)} ${totalDelta} ${refDebuffIconsHtml}
${refCards}
`; } } return `
${fmtTime(ev.timestamp, fightStart)}
${ev.abilityName} — ${fmtDmg(ev.totalDamage)} total ${debuffIconsHtml}
${targets}
${refHtml}
`; }).join(''); el.innerHTML = rows || '

Keine sichtbaren Targets

'; } function setEmpty(msg) { document.getElementById('analysis-loading').style.display = 'none'; document.getElementById('analysis-content').style.display = 'none'; document.getElementById('analysis-empty').style.display = 'block'; document.getElementById('analysis-empty-msg').textContent = msg; } let lastFightId = null; async function load() { const { reportCode, fightId, fightStart, fightEnd } = window.App ?? {}; if (!reportCode || !fightId) return; if (lastFightId === fightId) return; document.getElementById('analysis-loading').style.display = 'flex'; document.getElementById('analysis-empty').style.display = 'none'; document.getElementById('analysis-content').style.display = 'none'; let json; try { const res = await fetch('api/analysis.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ report_code: reportCode, fight_id: fightId, start_time: fightStart, end_time: fightEnd, language: window.App.language }), }); json = await res.json(); } catch (err) { setEmpty('Netzwerkfehler: ' + err.message); return; } if (json.reauth) { window.location.href = window.App?.authStartUrl?.() ?? 'auth/start.php'; return; } if (json.error) { setEmpty('Fehler: ' + json.error); return; } lastFightId = fightId; populateRefFightSelect(); setupPhases(window.App?.phases ?? []); renderPlayers(json.players ?? []); mitigationNames = json.mitigation_names ?? {}; renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart); document.getElementById('analysis-loading').style.display = 'none'; document.getElementById('analysis-content').style.display = 'block'; const exportBtn = document.getElementById('export-to-planner-btn'); if (exportBtn) exportBtn.style.display = ''; } window.analysisTab = { onFightSelected: load, onTabOpen: load, onFightsLoaded: onFightsLoaded, async selectSharedCompare(fightId, reportCode = '') { if (!fightId) return; if (reportCode && reportCode !== window.App?.reportCode) { refReportInput.value = reportCode; await loadExternalReport(reportCode, fightId); return; } if ([...refFightSelect.options].some(opt => parseInt(opt.value, 10) === fightId)) { refFightSelect.value = String(fightId); refFightSelect.dispatchEvent(new Event('change')); } }, exportForPlanner() { const fight = (window.App?.fights ?? []).find(f => f.id === window.App?.fightId); return { aoeEvents: lastEvents, fightStart: lastFightStart, phases: window.App?.phases ?? [], players: currentPlayers, fightName: fight?.name ?? `Fight ${window.App?.fightId ?? '?'}`, reportCode: window.App?.reportCode ?? '', fightId: window.App?.fightId ?? 0, fightEnd: window.App?.fightEnd ?? 0, mitigationNames, }; }, reset() { lastFightId = null; refEvents = []; refFightStart = 0; refPlayers = []; extFights = []; extReportCode = ''; mitigationNames = {}; document.getElementById('ref-player-section').style.display = 'none'; refFightSelect.value = ''; refFightSelect.style.display = 'none'; refExtFightSelect.value = ''; refExtFightSelect.style.display = 'none'; refFflogsLink.style.display = 'none'; refFflogsLink.href = '#'; refExtPanel.style.display = 'none'; const exportBtn = document.getElementById('export-to-planner-btn'); if (exportBtn) exportBtn.style.display = 'none'; }, }; document.getElementById('export-to-planner-btn')?.addEventListener('click', () => { window.plannerTab?.showImportModal(window.analysisTab.exportForPlanner()); }); })();