From e358a4e709c27dde71067f6eaf3ea30ec7ac3060 Mon Sep 17 00:00:00 2001 From: xziino Date: Sat, 23 May 2026 16:43:37 +0200 Subject: [PATCH] Planer: DR-Simulation + Schild-Eingabe in Mechanik-Cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ABILITY_DR-Map mit DR-Werten pro Ability (Feint = 5% magic) - simulateDrMultiplier() berechnet multiplikativen DR-Faktor - Mechanik-Cards zeigen mitigierten Wert (DR-only) + optional mit Schild - SGE/SCH Schild-Feld (in k) im Info-Panel, wird pro Plan gespeichert - Farbige Anzeige: grün = unter ∅ HP, rot = darüber Co-Authored-By: Claude Sonnet 4.6 --- css/planner.css | 37 +++++++++++++++++++++++++++ js/planner.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/css/planner.css b/css/planner.css index b5a10df..8236c3f 100644 --- a/css/planner.css +++ b/css/planner.css @@ -290,6 +290,11 @@ color: var(--t3); margin-left: 6px; } +.mechanic-mitig-row { margin-top: -2px; display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; } +.mechanic-mitig-shield { font-size: 12px; color: var(--t3); } +.mechanic-mitig-val { font-weight: 500; } +.mechanic-mitig--ok { color: var(--green); } +.mechanic-mitig--risk { color: var(--red); } .mechanic-assignments { display: flex; @@ -573,6 +578,38 @@ .info-warning--job { color: var(--t2); } .info-warning--missing { color: var(--red); } +.info-section--extra { margin-top: 12px; } + +.info-extra-row { + display: flex; + align-items: center; + gap: 8px; +} + +.info-extra-label { + font-size: 12px; + color: var(--t2); + flex: 1; +} + +.info-extra-input-wrap { + display: flex; + align-items: center; + gap: 4px; +} + +.info-extra-input { + width: 52px; + font-size: 13px; + padding: 3px 6px; + text-align: right; +} + +.info-extra-unit { + font-size: 12px; + color: var(--t3); +} + /* ── Folder Sidebar ──────────────────────────────────────────────────────────── */ .folder-section { margin-bottom: 2px; } diff --git a/js/planner.js b/js/planner.js index f859ef3..004895c 100644 --- a/js/planner.js +++ b/js/planner.js @@ -435,6 +435,15 @@ function avgNonTankMaxHp(plan) { return Math.round(hps.reduce((s, v) => s + v, 0) / hps.length); } +function simulateDrMultiplier(mechanic) { + let mult = 1; + for (const a of mechanic.assignments ?? []) { + if (a.buffType === 'shield') continue; + mult *= (1 - (ABILITY_DR[a.ability] ?? 0)); + } + return mult; +} + function renderMechanicListHtml(plan) { const mechanics = visiblePlanMechanics(plan); if (mechanics.length === 0) { @@ -483,6 +492,14 @@ function renderMechanicListHtml(plan) { : badgeHtml; }).join(''); + const drOnly = m.unmitigatedDamage ? Math.round(m.unmitigatedDamage * simulateDrMultiplier(m)) : 0; + const shieldVal = (plan.shieldK ?? 0) * 1000; + const mitigFull = Math.max(0, drOnly - shieldVal); + const hasDrAssign = (m.assignments ?? []).some(a => a.buffType !== 'shield' && (ABILITY_DR[a.ability] ?? 0) > 0); + const hasShield = shieldVal > 0; + const drOnlyCls = avgHp ? (drOnly <= avgHp ? 'mechanic-mitig--ok' : 'mechanic-mitig--risk') : ''; + const fullCls = avgHp ? (mitigFull <= avgHp ? 'mechanic-mitig--ok' : 'mechanic-mitig--risk') : ''; + return `
${escHtml(fmtTimestamp(m.timestamp))}
@@ -493,6 +510,10 @@ function renderMechanicListHtml(plan) { ? `
${fmtNumber(m.unmitigatedDamage)} unmitigiert${avgHp ? ` ∅ ${fmtNumber(avgHp)} HP` : ''}
` : '' } + ${hasDrAssign || hasShield ? `
+ ${hasDrAssign ? `→ ${fmtNumber(drOnly)} mitigiert` : ''} + ${hasShield ? `Mitigation mit Schild ${fmtNumber(mitigFull)}` : ''} +
` : ''}
${assignHtml}
${m.notes ? `
${escHtml(m.notes)}
` : ''}
Klicken zum Bearbeiten
@@ -1072,7 +1093,29 @@ function renderInfoPanel(plan) { warningsHtml += '
'; } - el.innerHTML = legendHtml + warningsHtml; + const extraInfoHtml = ` +
+
Zusätzliche Informationen
+
+ +
+ + k +
+
+
`; + + el.innerHTML = legendHtml + warningsHtml + extraInfoHtml; + + const shieldInput = el.querySelector('#plan-shield-k-input'); + if (shieldInput) { + shieldInput.addEventListener('input', () => { + const val = parseFloat(shieldInput.value) || 0; + updatePlan(plan.id, { shieldK: val > 0 ? val : null }); + refreshMechanicList(plan.id); + }); + } } function removeAssignment(planId, mechanicId, abilityName) { @@ -1468,6 +1511,27 @@ const MITIG_ICONS = { 'Improvised Finish': 'assets/icons/mitigation/improvised-finish.png', }; +// DR values (0–1) for buff/debuff mitigations — shields excluded (no reliable sim). +const ABILITY_DR = { + 'Passage of Arms': 0.15, + 'Troubadour': 0.15, + 'Tactician': 0.15, + 'Shield Samba': 0.15, + 'Dark Missionary': 0.10, + 'Heart of Light': 0.10, + 'Temperance': 0.10, + 'Sacred Soil': 0.10, + 'Expedient': 0.10, + 'Collective Unconscious': 0.10, + 'Holos': 0.10, + 'Kerachole': 0.10, + 'Magick Barrier': 0.10, + 'Fey Illumination': 0.05, + 'Reprisal': 0.10, + 'Feint': 0.05, // 10% phys / 5% magic — use magic (conservative) + 'Addle': 0.10, +}; + // 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 = [