Planner: Äquivalenz-Hinweise für fehlende Job-Abilities

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xziino 2026-05-22 12:36:16 +02:00
parent 84064669d3
commit d585f3be5a
2 changed files with 51 additions and 5 deletions

View File

@ -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; }

View File

@ -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 `<span class="badge badge-assign ${cls}${isMissing ? ' badge-assign--missing-job' : ''}"${title ? ` title="${title}"` : ''}>
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 = `<span class="badge badge-assign ${cls}${isMissing ? ' badge-assign--missing-job' : ''}"${title ? ` title="${title}"` : ''}>
${icon ? `<img class="badge-icon" src="${escHtml(icon)}" alt="">` : ''}
${label}
<button class="badge-remove" data-mechanic-id="${escHtml(m.id)}" data-ability="${escHtml(a.ability)}" title="Entfernen">×</button>
</span>`;
const hintHtml = suggestions.map(s =>
`<span class="badge-equiv-hint">→ ${escHtml(s.ability)} (${escHtml(s.job)})?</span>`
).join('');
return suggestions.length > 0
? `<span class="badge-with-hint">${badgeHtml}${hintHtml}</span>`
: 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) {