diff --git a/js/planner.js b/js/planner.js
index 64640e7..63bb773 100644
--- a/js/planner.js
+++ b/js/planner.js
@@ -150,6 +150,14 @@ function abilityIcon(ability) {
return MITIG_ICONS[ability] ?? actionMetaByName[ability]?.icon ?? '';
}
+function actualAbilityIcon(actual) {
+ const actionId = String(actual?.actionId ?? actual?.extraAbilityGameID ?? '');
+ return abilityIcon(actual?.ability)
+ || (actionId && actionMetaById[actionId]?.icon)
+ || actionMetaByName[actual?.name]?.icon
+ || '';
+}
+
function abilityShieldText(ability) {
return actionMetaByName[ability]?.shield ?? '';
}
@@ -3322,6 +3330,8 @@ function collectActualMitigations(event) {
ability,
name: mitigation.name ?? ability,
sourceJob,
+ actionId: mitigation.extraAbilityGameID ?? null,
+ extraAbilityGameID: mitigation.extraAbilityGameID ?? null,
appliedAt: Number.isFinite(Number(mitigation.appliedAt)) ? Number(mitigation.appliedAt) : null,
buffType: mitigation.buffType ?? abilityDefinition(ability)?.buffType ?? 'buff',
});
@@ -3336,6 +3346,8 @@ function collectActualCasts(casts, actualFightStart) {
name: cast.name ?? cast.key,
sourceJob: JOB_FROM_TYPE[cast.sourcePlayerType] ?? '',
sourceName: cast.sourceName ?? '',
+ actionId: cast.extraAbilityGameID ?? null,
+ extraAbilityGameID: cast.extraAbilityGameID ?? null,
timestamp: Number(cast.timestamp) || 0,
rel: (Number(cast.timestamp) || 0) - actualFightStart,
buffType: cast.buffType ?? abilityDefinition(cast.key ?? cast.name)?.buffType ?? 'buff',
@@ -3345,22 +3357,20 @@ function collectActualCasts(casts, actualFightStart) {
function actualMatchesAssignment(actual, assignment) {
if ((actual.ability ?? actual.name) !== assignment.ability) return false;
if (!assignment.job || !actual.sourceJob) return true;
- return actual.sourceJob === assignment.job;
+ if (actual.sourceJob === assignment.job) return true;
+ return jobCanUseAbility(actual.sourceJob, assignment.ability);
}
-function findCastForAssignment(actualCasts, consumedCasts, assignment, plannedStart, mechanicTime) {
+function findCastForAssignment(actualCasts, assignment, plannedStart, mechanicTime) {
const durationMs = assignmentDurationSeconds(assignment) * 1000;
const earlyWindow = Math.max(15000, durationMs + 5000);
const lateWindow = Math.max(10000, mechanicTime - plannedStart + 5000);
const candidates = actualCasts
.map((actual, idx) => ({ actual, idx, delta: actual.rel - plannedStart }))
- .filter(item => !consumedCasts.has(item.idx))
.filter(item => actualMatchesAssignment(item.actual, assignment))
.filter(item => item.delta >= -earlyWindow && item.delta <= lateWindow)
.sort((a, b) => Math.abs(a.delta) - Math.abs(b.delta));
- const match = candidates[0] ?? null;
- if (match) consumedCasts.add(match.idx);
- return match;
+ return candidates[0] ?? null;
}
function findActualMechanic(planMechanic, actualEvents, used, actualFightStart) {
@@ -3392,7 +3402,6 @@ function findActualMechanic(planMechanic, actualEvents, used, actualFightStart)
function comparePlanToAnalysis(plan, actualEvents, actualFightStart, actualCastEvents = []) {
const usedMechanics = new Set();
const actualCasts = collectActualCasts(actualCastEvents, actualFightStart);
- const consumedCasts = new Set();
const rows = [];
const summary = { ok: 0, early: 0, late: 0, missing: 0, extra: 0, unmatched: 0, unknown: 0 };
@@ -3409,7 +3418,7 @@ function comparePlanToAnalysis(plan, actualEvents, actualFightStart, actualCastE
const consumedActual = new Set();
const items = planned.map(assignment => {
const plannedStart = Number(assignment.sourceStart ?? assignmentStartMs(mechanic, assignment)) || 0;
- const castMatch = findCastForAssignment(actualCasts, consumedCasts, assignment, plannedStart, mechanic.timestamp);
+ const castMatch = findCastForAssignment(actualCasts, assignment, plannedStart, mechanic.timestamp);
if (!castMatch) {
summary.missing += 1;
return { status: 'missing', assignment };
@@ -3497,7 +3506,7 @@ function compareAssignmentChip(assignment, plan) {
}
function compareActualChip(actual, plan, status = 'extra', delta = null, note = '') {
- const icon = abilityIcon(actual.ability);
+ const icon = actualAbilityIcon(actual);
const name = localizedAbilityName(actual.ability, plan);
const deltaHtml = delta == null || status === 'ok' ? '' : `${escHtml(compareDeltaText(delta))}`;
const noteHtml = note ? `${escHtml(note)}` : '';
@@ -3753,6 +3762,7 @@ function initCompareModal() {
const json = await res.json();
if (json.reauth) { window.location.href = window.App?.authStartUrl?.() ?? 'auth/start.php'; return; }
if (json.error) throw new Error(json.error);
+ await ensureActionMetaLoaded();
compareLastPlan = plan;
compareLastResult = comparePlanToAnalysis(plan, json.aoe_events ?? [], Number(fight.startTime) || 0, json.mitigation_casts ?? []);
renderLastCompare();