diff --git a/js/analysis.js b/js/analysis.js index 7a71b95..d690e48 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -1,102 +1,5 @@ (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', - }; - - // ability name → jobs that can provide it (for job-based ref comparison) - const ABILITY_JOBS = { - 'Passage of Arms': ['PLD'], - 'Divine Veil': ['PLD'], - 'Guardian': ['PLD'], - 'Reprisal': ['PLD', 'WAR', 'DRK', 'GNB'], - 'Shake It Off': ['WAR'], - 'Bloodwhetting': ['WAR'], - 'Dark Missionary': ['DRK'], - 'Heart of Light': ['GNB'], - 'Temperance': ['WHM'], - 'Divine Benison': ['WHM'], - 'Divine Caress': ['WHM'], - 'Sacred Soil': ['SCH'], - 'Expedient': ['SCH'], - 'Fey Illumination': ['SCH'], - 'Galvanize': ['SCH'], - 'Seraphic Veil': ['SCH'], - 'Catalyze': ['SCH'], - 'Collective Unconscious': ['AST'], - 'Neutral Sect': ['AST'], - 'Intersection': ['AST'], - 'the Spire': ['AST'], - 'Kerachole': ['SGE'], - 'Holos': ['SGE'], - 'Holosakos': ['SGE'], - 'Panhaima': ['SGE'], - 'Eukrasian Prognosis': ['SGE'], - 'Eukrasian Prognosis II': ['SGE'], - 'Eukrasian Diagnosis': ['SGE'], - 'Differential Diagnosis': ['SGE'], - 'Haima': ['SGE'], - 'Troubadour': ['BRD'], - 'Tactician': ['MCH'], - 'Shield Samba': ['DNC'], - 'Improvised Finish': ['DNC'], - 'Feint': ['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR'], - 'Addle': ['BLM', 'SMN', 'RDM', 'PCT'], - 'Radiant Aegis': ['SMN'], - 'Magick Barrier': ['RDM'], - 'Tempera Coat': ['PCT'], - 'Tempera Grassa': ['PCT'], - }; + const { MITIG_ICONS, JOB_ABBR, ABILITY_JOBS, JOB_ROLE } = window.FF14_DATA; // Deduplicated list of all mitigations across all targets of a ref event function collectRefMitigs(refEvent) { @@ -509,11 +412,6 @@ 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 []; } @@ -528,7 +426,7 @@ const players = jobComp.map((job, i) => ({ job, name: roster[i]?.name ?? '', - role: PLAN_JOB_ROLE[job] ?? 'dps', + role: JOB_ROLE[job] ?? 'dps', })).filter(p => p.name && p.job); return plan.mechanics.map(m => { @@ -610,7 +508,7 @@ .map((job, i) => { const name = plan.playerRoster?.[i]?.name ?? ''; if (!name || !job) return null; - return { name, type: job, role: PLAN_JOB_ROLE[job] ?? 'dps' }; + return { name, type: job, role: JOB_ROLE[job] ?? 'dps' }; }) .filter(Boolean); diff --git a/js/ffxiv-data.js b/js/ffxiv-data.js new file mode 100644 index 0000000..4b4767b --- /dev/null +++ b/js/ffxiv-data.js @@ -0,0 +1,212 @@ +(function () { + const JOB_FROM_TYPE = { + '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', + }; + + const JOB_ROLE = { + 'PLD': 'tank', 'WAR': 'tank', 'DRK': 'tank', 'GNB': 'tank', + 'WHM': 'healer', 'SCH': 'healer', 'AST': 'healer', 'SGE': 'healer', + 'MNK': 'dps', 'DRG': 'dps', 'NIN': 'dps', 'SAM': 'dps', + 'RPR': 'dps', 'VPR': 'dps', 'BRD': 'dps', 'MCH': 'dps', + 'DNC': 'dps', 'BLM': 'dps', 'SMN': 'dps', 'RDM': 'dps', 'PCT': 'dps', + 'BLU': 'dps', + }; + + const ALL_JOBS = [ + { group: 'Tank', jobs: ['PLD', 'WAR', 'DRK', 'GNB'] }, + { group: 'Healer', jobs: ['WHM', 'SCH', 'AST', 'SGE'] }, + { group: 'Melee', jobs: ['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR'] }, + { group: 'Ranged', jobs: ['BRD', 'MCH', 'DNC'] }, + { group: 'Caster', jobs: ['BLM', 'SMN', 'RDM', 'PCT'] }, + ]; + + const JOB_ABILITIES = { + 'PLD': [ + { name: 'Passage of Arms', buffType: 'buff' }, + { name: 'Divine Veil', buffType: 'shield' }, + { name: 'Guardian', buffType: 'shield' }, + { name: 'Reprisal', buffType: 'debuff' }, + ], + 'WAR': [ + { name: 'Shake It Off', buffType: 'shield' }, + { name: 'Bloodwhetting', buffType: 'shield' }, + { name: 'Reprisal', buffType: 'debuff' }, + ], + 'DRK': [ + { name: 'Dark Missionary', buffType: 'buff' }, + { name: 'Reprisal', buffType: 'debuff' }, + ], + 'GNB': [ + { name: 'Heart of Light', buffType: 'buff' }, + { name: 'Reprisal', buffType: 'debuff' }, + ], + 'WHM': [ + { name: 'Temperance', buffType: 'buff' }, + { name: 'Divine Benison', buffType: 'shield' }, + { name: 'Divine Caress', buffType: 'shield' }, + ], + 'SCH': [ + { name: 'Sacred Soil', buffType: 'buff' }, + { name: 'Expedient', buffType: 'buff' }, + { name: 'Fey Illumination', buffType: 'buff' }, + { name: 'Galvanize', buffType: 'shield' }, + { name: 'Seraphic Veil', buffType: 'shield' }, + { name: 'Catalyze', buffType: 'shield' }, + ], + 'AST': [ + { name: 'Collective Unconscious', buffType: 'buff' }, + { name: 'Neutral Sect', buffType: 'shield' }, + { name: 'Intersection', buffType: 'shield' }, + { name: 'the Spire', buffType: 'shield' }, + ], + 'SGE': [ + { name: 'Kerachole', buffType: 'buff' }, + { name: 'Holos', buffType: 'buff' }, + { name: 'Holosakos', buffType: 'shield' }, + { name: 'Panhaima', buffType: 'shield' }, + { name: 'Eukrasian Prognosis', buffType: 'shield' }, + { name: 'Eukrasian Prognosis II', buffType: 'shield' }, + { name: 'Eukrasian Diagnosis', buffType: 'shield' }, + { name: 'Differential Diagnosis', buffType: 'shield' }, + { name: 'Haima', buffType: 'shield' }, + ], + 'BRD': [{ name: 'Troubadour', buffType: 'buff' }], + 'MCH': [{ name: 'Tactician', buffType: 'buff' }], + 'DNC': [ + { name: 'Shield Samba', buffType: 'buff' }, + { name: 'Improvised Finish', buffType: 'shield' }, + ], + 'MNK': [{ name: 'Feint', buffType: 'debuff' }], + 'DRG': [{ name: 'Feint', buffType: 'debuff' }], + 'NIN': [{ name: 'Feint', buffType: 'debuff' }], + 'SAM': [{ name: 'Feint', buffType: 'debuff' }], + 'RPR': [{ name: 'Feint', buffType: 'debuff' }], + 'VPR': [{ name: 'Feint', buffType: 'debuff' }], + 'BLM': [{ name: 'Addle', buffType: 'debuff' }], + 'SMN': [ + { name: 'Addle', buffType: 'debuff' }, + { name: 'Radiant Aegis', buffType: 'shield' }, + ], + 'RDM': [ + { name: 'Addle', buffType: 'debuff' }, + { name: 'Magick Barrier', buffType: 'buff' }, + ], + 'PCT': [ + { name: 'Addle', buffType: 'debuff' }, + { name: 'Tempera Coat', buffType: 'shield' }, + { name: 'Tempera Grassa', buffType: 'shield' }, + ], + }; + + const ABILITY_JOB_MAP = { + 'Passage of Arms': 'PLD', 'Divine Veil': 'PLD', 'Guardian': 'PLD', + 'Shake It Off': 'WAR', 'Bloodwhetting': 'WAR', + 'Dark Missionary': 'DRK', + 'Heart of Light': 'GNB', + 'Temperance': 'WHM', 'Divine Benison': 'WHM', 'Divine Caress': 'WHM', + 'Sacred Soil': 'SCH', 'Expedient': 'SCH', 'Fey Illumination': 'SCH', + 'Galvanize': 'SCH', 'Seraphic Veil': 'SCH', 'Catalyze': 'SCH', + 'Collective Unconscious': 'AST', 'Neutral Sect': 'AST', + 'Intersection': 'AST', 'the Spire': 'AST', + 'Kerachole': 'SGE', 'Holos': 'SGE', 'Holosakos': 'SGE', + 'Panhaima': 'SGE', 'Haima': 'SGE', + 'Eukrasian Prognosis': 'SGE', 'Eukrasian Prognosis II': 'SGE', + 'Eukrasian Diagnosis': 'SGE', 'Differential Diagnosis': 'SGE', + 'Troubadour': 'BRD', + 'Tactician': 'MCH', + 'Shield Samba': 'DNC', 'Improvised Finish': 'DNC', + 'Radiant Aegis': 'SMN', + 'Magick Barrier': 'RDM', + 'Tempera Coat': 'PCT', 'Tempera Grassa': 'PCT', + }; + + const MITIG_ICONS = { + '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', + 'Reprisal': 'assets/icons/mitigation/reprisal.png', + 'Feint': 'assets/icons/mitigation/feint.png', + 'Addle': 'assets/icons/mitigation/addle.png', + '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 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, + 'Addle': 0.10, + }; + + const ABILITY_JOBS = {}; + Object.entries(JOB_ABILITIES).forEach(([job, abilities]) => { + abilities.forEach(ability => { + if (!ABILITY_JOBS[ability.name]) ABILITY_JOBS[ability.name] = []; + ABILITY_JOBS[ability.name].push(job); + }); + }); + + window.FF14_DATA = { + JOB_ABBR: JOB_FROM_TYPE, + JOB_FROM_TYPE, + JOB_ROLE, + ALL_JOBS, + JOB_ABILITIES, + ABILITY_JOB_MAP, + ABILITY_JOBS, + ABILITY_DR, + MITIG_ICONS, + TANK_JOBS: new Set(['PLD', 'WAR', 'DRK', 'GNB']), + MELEE_JOBS: new Set(['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR']), + CASTER_JOBS: new Set(['BLM', 'SMN', 'RDM', 'PCT']), + }; +})(); diff --git a/js/planner.js b/js/planner.js index 0ee2cae..e3a7f3c 100644 --- a/js/planner.js +++ b/js/planner.js @@ -444,18 +444,18 @@ function simulateDrMultiplier(mechanic, assignments = mechanic.assignments ?? [] return mult; } -function effectiveAssignmentsForMechanic(plan, targetMechanic) { +function plannedAssignmentsForMechanic(plan, targetMechanic) { + const mechanics = visiblePlanMechanics(plan); const result = []; - const seen = new Set(); for (const entry of canonicalAssignmentActivations(plan, { dedupeKey: canonicalMechanicKey })) { - if (targetMechanic.timestamp < entry.start || targetMechanic.timestamp > entry.end) continue; - const assignment = entry.assignment; - const key = canonicalMechanicKey(entry); - if (seen.has(key)) continue; - seen.add(key); + const displayMechanic = mechanics.reduce((best, mechanic) => + Math.abs(mechanic.timestamp - entry.start) < Math.abs(best.timestamp - entry.start) ? mechanic : best + , mechanics[0]); + if (displayMechanic?.id !== targetMechanic.id) continue; + result.push({ - ...assignment, + ...entry.assignment, sourceMechanicId: entry.mechanic.id, sourceStart: entry.start, }); @@ -482,8 +482,8 @@ function renderMechanicListHtml(plan) { const avgHp = avgNonTankMaxHp(plan); return mechanics.map(m => { - const effective = effectiveAssignmentsForMechanic(plan, m); - const sorted = sortedAssignments(effective); + const planned = plannedAssignmentsForMechanic(plan, m); + const sorted = sortedAssignments(planned); const assignHtml = sorted.length === 0 ? 'Keine Zuweisung' : sorted.map(a => { @@ -513,10 +513,10 @@ function renderMechanicListHtml(plan) { : badgeHtml; }).join(''); - const drOnly = m.unmitigatedDamage ? Math.round(m.unmitigatedDamage * simulateDrMultiplier(m, effective)) : 0; + const drOnly = m.unmitigatedDamage ? Math.round(m.unmitigatedDamage * simulateDrMultiplier(m, planned)) : 0; const shieldVal = (plan.shieldK ?? 0) * 1000; const mitigFull = Math.max(0, drOnly - shieldVal); - const hasDrAssign = effective.some(a => a.buffType !== 'shield' && (ABILITY_DR[a.ability] ?? 0) > 0); + const hasDrAssign = planned.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') : ''; @@ -1589,199 +1589,20 @@ function initNewFolderForm() { }); } -// ── Ability → Job mapping ───────────────────────────────────────────────────── - -const ABILITY_JOB_MAP = { - 'Passage of Arms': 'PLD', 'Divine Veil': 'PLD', 'Guardian': 'PLD', - 'Shake It Off': 'WAR', 'Bloodwhetting': 'WAR', - 'Dark Missionary': 'DRK', - 'Heart of Light': 'GNB', - 'Temperance': 'WHM', 'Divine Benison': 'WHM', 'Divine Caress': 'WHM', - 'Sacred Soil': 'SCH', 'Expedient': 'SCH', 'Fey Illumination': 'SCH', - 'Galvanize': 'SCH', 'Seraphic Veil': 'SCH', 'Catalyze': 'SCH', - 'Collective Unconscious': 'AST', 'Neutral Sect': 'AST', - 'Intersection': 'AST', 'the Spire': 'AST', - 'Kerachole': 'SGE', 'Holos': 'SGE', 'Holosakos': 'SGE', - 'Panhaima': 'SGE', 'Haima': 'SGE', - 'Eukrasian Prognosis': 'SGE', - 'Eukrasian Diagnosis': 'SGE', 'Differential Diagnosis': 'SGE', - 'Troubadour': 'BRD', - 'Tactician': 'MCH', - 'Shield Samba': 'DNC', 'Improvised Finish': 'DNC', - 'Radiant Aegis': 'SMN', - 'Magick Barrier': 'RDM', - 'Tempera Coat': 'PCT', 'Tempera Grassa': 'PCT', -}; - -const JOB_FROM_TYPE = { - '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', -}; - -const TANK_JOBS = new Set(['PLD', 'WAR', 'DRK', 'GNB']); -const MELEE_JOBS = new Set(['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR']); -const CASTER_JOBS = new Set(['BLM', 'SMN', 'RDM', 'PCT']); - -const JOB_ROLE = { - 'PLD': 'tank', 'WAR': 'tank', 'DRK': 'tank', 'GNB': 'tank', - 'WHM': 'healer', 'SCH': 'healer', 'AST': 'healer', 'SGE': 'healer', - 'MNK': 'dps', 'DRG': 'dps', 'NIN': 'dps', 'SAM': 'dps', - 'RPR': 'dps', 'VPR': 'dps', 'BRD': 'dps', 'MCH': 'dps', - 'DNC': 'dps', 'BLM': 'dps', 'SMN': 'dps', 'RDM': 'dps', 'PCT': 'dps', -}; - -const ALL_JOBS = [ - { group: 'Tank', jobs: ['PLD', 'WAR', 'DRK', 'GNB'] }, - { group: 'Healer', jobs: ['WHM', 'SCH', 'AST', 'SGE'] }, - { group: 'Melee', jobs: ['MNK', 'DRG', 'NIN', 'SAM', 'RPR', 'VPR'] }, - { group: 'Ranged', jobs: ['BRD', 'MCH', 'DNC'] }, - { group: 'Caster', jobs: ['BLM', 'SMN', 'RDM', 'PCT'] }, -]; - -const JOB_ABILITIES = { - 'PLD': [ - { name: 'Passage of Arms', buffType: 'buff' }, - { name: 'Divine Veil', buffType: 'shield' }, - { name: 'Guardian', buffType: 'shield' }, - { name: 'Reprisal', buffType: 'debuff' }, - ], - 'WAR': [ - { name: 'Shake It Off', buffType: 'shield' }, - { name: 'Bloodwhetting', buffType: 'shield' }, - { name: 'Reprisal', buffType: 'debuff' }, - ], - 'DRK': [ - { name: 'Dark Missionary', buffType: 'buff' }, - { name: 'Reprisal', buffType: 'debuff' }, - ], - 'GNB': [ - { name: 'Heart of Light', buffType: 'buff' }, - { name: 'Reprisal', buffType: 'debuff' }, - ], - 'WHM': [ - { name: 'Temperance', buffType: 'buff' }, - { name: 'Divine Benison', buffType: 'shield' }, - { name: 'Divine Caress', buffType: 'shield' }, - ], - 'SCH': [ - { name: 'Sacred Soil', buffType: 'buff' }, - { name: 'Expedient', buffType: 'buff' }, - { name: 'Fey Illumination', buffType: 'buff' }, - { name: 'Galvanize', buffType: 'shield' }, - { name: 'Seraphic Veil', buffType: 'shield' }, - { name: 'Catalyze', buffType: 'shield' }, - ], - 'AST': [ - { name: 'Collective Unconscious', buffType: 'buff' }, - { name: 'Neutral Sect', buffType: 'shield' }, - { name: 'Intersection', buffType: 'shield' }, - { name: 'the Spire', buffType: 'shield' }, - ], - 'SGE': [ - { name: 'Kerachole', buffType: 'buff' }, - { name: 'Holos', buffType: 'buff' }, - { name: 'Holosakos', buffType: 'shield' }, - { name: 'Panhaima', buffType: 'shield' }, - { name: 'Eukrasian Prognosis', buffType: 'shield' }, - { name: 'Eukrasian Diagnosis', buffType: 'shield' }, - { name: 'Differential Diagnosis', buffType: 'shield' }, - { name: 'Haima', buffType: 'shield' }, - ], - 'BRD': [{ name: 'Troubadour', buffType: 'buff' }], - 'MCH': [{ name: 'Tactician', buffType: 'buff' }], - 'DNC': [ - { name: 'Shield Samba', buffType: 'buff' }, - { name: 'Improvised Finish', buffType: 'shield' }, - ], - 'MNK': [{ name: 'Feint', buffType: 'debuff' }], - 'DRG': [{ name: 'Feint', buffType: 'debuff' }], - 'NIN': [{ name: 'Feint', buffType: 'debuff' }], - 'SAM': [{ name: 'Feint', buffType: 'debuff' }], - 'RPR': [{ name: 'Feint', buffType: 'debuff' }], - 'VPR': [{ name: 'Feint', buffType: 'debuff' }], - 'BLM': [{ name: 'Addle', buffType: 'debuff' }], - 'SMN': [ - { name: 'Addle', buffType: 'debuff' }, - { name: 'Radiant Aegis', buffType: 'shield' }, - ], - 'RDM': [ - { name: 'Addle', buffType: 'debuff' }, - { name: 'Magick Barrier', buffType: 'buff' }, - ], - 'PCT': [ - { name: 'Addle', buffType: 'debuff' }, - { name: 'Tempera Coat', buffType: 'shield' }, - { name: 'Tempera Grassa', buffType: 'shield' }, - ], -}; - -const MITIG_ICONS = { - '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', - 'Reprisal': 'assets/icons/mitigation/reprisal.png', - 'Feint': 'assets/icons/mitigation/feint.png', - 'Addle': 'assets/icons/mitigation/addle.png', - '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', -}; - -// 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, -}; +// ── Shared FFXIV metadata ───────────────────────────────────────────────────── +const { + ABILITY_DR, + ABILITY_JOB_MAP, + ALL_JOBS, + CASTER_JOBS, + JOB_ABILITIES, + JOB_FROM_TYPE, + JOB_ROLE, + MELEE_JOBS, + MITIG_ICONS, + TANK_JOBS, +} = window.FF14_DATA; // 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 = [ diff --git a/templates/page.php b/templates/page.php index 249c768..79c630e 100644 --- a/templates/page.php +++ b/templates/page.php @@ -122,6 +122,7 @@ +