From d585f3be5a17a4aaebaf86d3669d226c33051062 Mon Sep 17 00:00:00 2001 From: xziino Date: Fri, 22 May 2026 12:36:16 +0200 Subject: [PATCH] =?UTF-8?q?Planner:=20=C3=84quivalenz-Hinweise=20f=C3=BCr?= =?UTF-8?q?=20fehlende=20Job-Abilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- css/planner.css | 16 ++++++++++++++++ js/planner.js | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/css/planner.css b/css/planner.css index c06fbb9..5ccdb1f 100644 --- a/css/planner.css +++ b/css/planner.css @@ -417,6 +417,22 @@ .ability-chip--other-job { opacity: 0.45; } +/* ── Equivalents hint ────────────────────────────────────────────────────────── */ +.badge-with-hint { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; +} + +.badge-equiv-hint { + font-size: 11px; + color: var(--red); + white-space: nowrap; + padding: 0 2px; + cursor: default; +} + /* ── Folder Sidebar ──────────────────────────────────────────────────────────── */ .folder-section { margin-bottom: 2px; } diff --git a/js/planner.js b/js/planner.js index c565196..f9aae8e 100644 --- a/js/planner.js +++ b/js/planner.js @@ -350,15 +350,22 @@ function renderMechanicListHtml(plan) { const cls = a.buffType === 'debuff' ? 'badge-assign-debuff' : a.buffType === 'shield' ? 'badge-assign-shield' : 'badge-assign-buff'; - const isMissing = !!a.job && !activeJobSet.has(a.job); - const icon = MITIG_ICONS[a.ability] ?? ''; - const label = a.job ? `${escHtml(a.job)} · ${escHtml(a.ability)}` : escHtml(a.ability); - const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : ''; - return ` + const isMissing = !!a.job && !activeJobSet.has(a.job); + const icon = MITIG_ICONS[a.ability] ?? ''; + const label = a.job ? `${escHtml(a.job)} · ${escHtml(a.ability)}` : escHtml(a.ability); + const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : ''; + const suggestions = isMissing ? findEquivSuggestions(a.ability, activeJobSet) : []; + const badgeHtml = ` ${icon ? `` : ''} ${label} `; + const hintHtml = suggestions.map(s => + `→ ${escHtml(s.ability)} (${escHtml(s.job)})?` + ).join(''); + return suggestions.length > 0 + ? `${badgeHtml}${hintHtml}` + : badgeHtml; }).join(''); return ` @@ -820,6 +827,29 @@ const MITIG_ICONS = { 'Improvised Finish': 'assets/icons/mitigation/improvised-finish.png', }; +// 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 = [ + ['Troubadour', 'Tactician', 'Shield Samba'], + ['Sacred Soil', 'Kerachole'], +]; + +function findEquivSuggestions(abilityName, activeJobSet) { + const group = ABILITY_EQUIVALENTS.find(g => g.includes(abilityName)); + if (!group) return []; + const suggestions = []; + for (const equiv of group) { + if (equiv === abilityName) continue; + for (const job of activeJobSet) { + if ((JOB_ABILITIES[job] ?? []).some(a => a.name === equiv)) { + suggestions.push({ ability: equiv, job }); + break; + } + } + } + return suggestions; +} + const ASSIGN_ORDER = { debuff: 0, buff: 1, shield: 2 }; function sortedAssignments(assignments) {