From fb58226be8e70ccf37a5714f2d5342bcdafcc1f4 Mon Sep 17 00:00:00 2001 From: xziino Date: Sat, 23 May 2026 08:30:52 +0200 Subject: [PATCH] Analyse-Tab: Plan als Referenz-Quelle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pläne aus localStorage als Ref-Quelle auswählbar ("+Plan als Referenz") - planToRefEvents() konvertiert Plan-Mechaniken ins refEvents-Format - PLAN-Label statt REF, kein Delta, kein Schadenswert - Buff-Icons mit "fehlt"-Markierung, Debuffs im Header - Shield-Assignments als "Schild"-Text mit Tooltip - Schließt sich mit anderen Ref-Quellen gegenseitig aus Co-Authored-By: Claude Sonnet 4.6 --- js/analysis.js | 148 ++++++++++++++++++++++++++++++++++--- templates/tab-analysis.php | 9 +++ 2 files changed, 146 insertions(+), 11 deletions(-) diff --git a/js/analysis.js b/js/analysis.js index 4f54dc4..c02b3b3 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -161,6 +161,7 @@ let extFights = []; let extReportCode = ''; let mitigationNames = {}; + let planRefId = ''; // ── Player grid ────────────────────────────────────────────────────────── @@ -301,8 +302,11 @@ const fight = (window.App?.fights ?? []).find(f => f.id === refId); if (!fight) return; - // Clear ext-report selection - refExtFightSelect.value = ''; + // Clear ext-report and plan selections + refExtFightSelect.value = ''; + planRefId = ''; + refPlanSelect.value = ''; + refPlanPanel.style.display = 'none'; refFightSelect.disabled = true; try { @@ -446,8 +450,11 @@ const fight = extFights.find(f => f.id === refId); if (!fight) return; - // Clear same-report selection - refFightSelect.value = ''; + // Clear same-report and plan selections + refFightSelect.value = ''; + planRefId = ''; + refPlanSelect.value = ''; + refPlanPanel.style.display = 'none'; refExtFightSelect.disabled = true; try { @@ -485,6 +492,115 @@ await loadExternalCompare(refId); }); + // ── Plan as reference ───────────────────────────────────────────────────── + + const refPlanToggle = document.getElementById('ref-plan-toggle'); + const refPlanPanel = document.getElementById('ref-plan-panel'); + const refPlanSelect = document.getElementById('ref-plan-select'); + + const PLAN_JOB_ROLE = { + 'PLD': 'tank', 'WAR': 'tank', 'DRK': 'tank', 'GNB': 'tank', + 'WHM': 'healer', 'SCH': 'healer', 'AST': 'healer', 'SGE': 'healer', + }; + + function loadPlansForRef() { + try { return JSON.parse(localStorage.getItem('ff14-planner-plans') || '[]'); } + catch { return []; } + } + + function planToRefEvents(plan) { + const roster = plan.playerRoster ?? []; + const jobComp = plan.jobComposition ?? []; + const fightStart = plan.source?.fightStart ?? 0; + const mitigNames = plan.mitigationNames ?? {}; + + const players = jobComp.map((job, i) => ({ + job, + name: roster[i]?.name ?? '', + role: PLAN_JOB_ROLE[job] ?? 'dps', + })).filter(p => p.name && p.job); + + return plan.mechanics.map(m => { + const mitigations = (m.assignments ?? []).map(a => ({ + key: a.ability, + name: a.abilityName || mitigNames[a.ability] || a.ability, + buffType: a.buffType, + dr: 0, + })); + + const targets = players.map(p => ({ + id: 0, + name: p.name, + type: p.job, + role: p.role, + amount: 0, + absorbed: 0, + overkill: 0, + hp: 0, + maxHp: 0, + unmitigatedAmount: 0, + mitigations, + })); + + return { + abilityName: m.name, + abilityId: m.abilityId ?? 0, + timestamp: fightStart + m.timestamp, + totalDamage: 0, + targets, + isPlanRef: true, + }; + }); + } + + function populateRefPlanSelect() { + const plans = loadPlansForRef(); + refPlanSelect.innerHTML = ''; + plans.forEach(p => { + const opt = document.createElement('option'); + opt.value = p.id; + opt.textContent = `${p.name} (${p.mechanics.length} Mechaniken)`; + refPlanSelect.appendChild(opt); + }); + refPlanSelect.value = planRefId || ''; + } + + refPlanToggle.addEventListener('click', () => { + const hidden = refPlanPanel.style.display === 'none'; + refPlanPanel.style.display = hidden ? '' : 'none'; + if (hidden) populateRefPlanSelect(); + }); + + refPlanSelect.addEventListener('change', () => { + const id = refPlanSelect.value; + + // Clear other ref sources + refFightSelect.value = ''; + refExtFightSelect.value = ''; + updateRefFflogsLink(0); + + if (!id) { + planRefId = ''; + refEvents = []; + refFightStart = 0; + refPlayers = []; + renderRefPlayers(); + renderTimeline(lastEvents, lastFightStart); + return; + } + + const plan = loadPlansForRef().find(p => p.id === id); + if (!plan) return; + + planRefId = id; + refEvents = planToRefEvents(plan); + refFightStart = plan.source?.fightStart ?? 0; + refPlayers = []; + + renderRefPlayers(); + renderTimeline(lastEvents, lastFightStart); + }); + // ── Timeline rendering ──────────────────────────────────────────────────── function renderTimeline(events, fightStart) { @@ -630,10 +746,12 @@ return `${m.name}`; }).join(''); + const isPlanRef = !!refEv.isPlanRef; + 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 diff = (!isPlanRef && curr) ? curr.amount - t.amount : 0; + const dead = !isPlanRef && t.hp === 0 && t.maxHp > 0; const deltaHtml = diff !== 0 ? `${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}` @@ -658,11 +776,15 @@ 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); + const isMissing = !isPlanRef && currentGroupHasJob && !currentEventMitigKeys.has(k); return isMissing ? `${s.name} [fehlt im aktuellen Pull]` : s.name; }).join('\n') : null; + const absorbedHtml = isPlanRef + ? (refShields.length ? ` Schild` : '') + : (t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''); + return `
@@ -671,20 +793,21 @@ ${deltaHtml}
${t.name} - ${fmtDmg(t.amount)}${t.absorbed > 0 ? ` +${fmtDmg(t.absorbed)}` : ''} + ${isPlanRef ? '' : fmtDmg(t.amount)}${absorbedHtml}
${refMitigIcons ? `
${refMitigIcons}
` : ''} `; }).join(''); const totalDiff = ev.totalDamage - refEv.totalDamage; - const totalDelta = totalDiff !== 0 + const totalDelta = (!isPlanRef && totalDiff !== 0) ? `${totalDiff > 0 ? '+' : ''}${fmtDmg(totalDiff)}` : ''; + const refLabel = isPlanRef ? 'PLAN' : `REF ${fmtDmg(refEv.totalDamage)} ${totalDelta}`; refHtml = `
- REF ${fmtDmg(refEv.totalDamage)} ${totalDelta} ${refDebuffIconsHtml} + ${refLabel} ${refDebuffIconsHtml}
${refCards}
`; } @@ -822,7 +945,7 @@ mitigationNames, }; }, - hasRefExport() { return refEvents.length > 0; }, + hasRefExport() { return refEvents.length > 0 && !planRefId; }, reset() { lastFightId = null; refEvents = []; @@ -831,6 +954,7 @@ extFights = []; extReportCode = ''; mitigationNames = {}; + planRefId = ''; document.getElementById('ref-player-section').style.display = 'none'; refFightSelect.value = ''; refFightSelect.style.display = 'none'; @@ -839,6 +963,8 @@ refFflogsLink.style.display = 'none'; refFflogsLink.href = '#'; refExtPanel.style.display = 'none'; + refPlanPanel.style.display = 'none'; + refPlanSelect.value = ''; const exportBtn = document.getElementById('export-to-planner-btn'); if (exportBtn) exportBtn.style.display = 'none'; }, diff --git a/templates/tab-analysis.php b/templates/tab-analysis.php index e1ad5ff..840a23b 100644 --- a/templates/tab-analysis.php +++ b/templates/tab-analysis.php @@ -22,6 +22,15 @@
REF Spieler
+
+ + +
+