fix timeline skills vocer multiple skills
This commit is contained in:
parent
0f8a90d1b4
commit
fd0de86dbc
201
js/planner.js
201
js/planner.js
@ -445,14 +445,12 @@ function simulateDrMultiplier(mechanic, assignments = mechanic.assignments ?? []
|
|||||||
}
|
}
|
||||||
|
|
||||||
function plannedAssignmentsForMechanic(plan, targetMechanic) {
|
function plannedAssignmentsForMechanic(plan, targetMechanic) {
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
|
||||||
const result = [];
|
const result = [];
|
||||||
|
const targetTime = Number(targetMechanic.timestamp);
|
||||||
|
const tolerance = 50;
|
||||||
|
|
||||||
for (const entry of canonicalAssignmentActivations(plan, { dedupeKey: canonicalMechanicKey })) {
|
for (const entry of canonicalAssignmentActivations(plan, { dedupeKey: canonicalMechanicKey })) {
|
||||||
const displayMechanic = mechanics.reduce((best, mechanic) =>
|
if (targetTime < entry.start - tolerance || targetTime > entry.end + tolerance) continue;
|
||||||
Math.abs(mechanic.timestamp - entry.start) < Math.abs(best.timestamp - entry.start) ? mechanic : best
|
|
||||||
, mechanics[0]);
|
|
||||||
if (displayMechanic?.id !== targetMechanic.id) continue;
|
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
...entry.assignment,
|
...entry.assignment,
|
||||||
@ -677,6 +675,136 @@ function canonicalAssignmentActivations(plan, { dedupeKey = canonicalMechanicKey
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignmentEntryForRef(plan, ref) {
|
||||||
|
if (!plan || !ref) return null;
|
||||||
|
const mechanic = (plan.mechanics ?? []).find(m => m.id === ref.mechanicId);
|
||||||
|
if (!mechanic) return null;
|
||||||
|
const assignment = (mechanic.assignments ?? []).find(a =>
|
||||||
|
a.ability === ref.ability && (a.job ?? '') === ref.job
|
||||||
|
);
|
||||||
|
if (!assignment) return null;
|
||||||
|
const start = assignmentStartMs(mechanic, assignment);
|
||||||
|
return {
|
||||||
|
mechanic,
|
||||||
|
assignment,
|
||||||
|
start,
|
||||||
|
end: start + assignmentWindowMs(assignment),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignmentRowKeys(plan, assignment) {
|
||||||
|
const assignedJob = assignment.job ?? '';
|
||||||
|
if (assignedJob) return new Set([`${assignedJob}::${assignment.ability}`]);
|
||||||
|
|
||||||
|
const jobs = (plan.jobComposition ?? []).filter(job => jobCanUseAbility(job, assignment.ability));
|
||||||
|
return new Set(jobs.map(job => `${job}::${assignment.ability}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignmentEntriesShareRow(plan, left, right) {
|
||||||
|
const leftRows = assignmentRowKeys(plan, left.assignment);
|
||||||
|
const rightRows = assignmentRowKeys(plan, right.assignment);
|
||||||
|
for (const row of leftRows) {
|
||||||
|
if (rightRows.has(row)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignmentsOverlapActiveFrame(plan, left, right) {
|
||||||
|
return left.assignment.ability === right.assignment.ability
|
||||||
|
&& assignmentEntriesShareRow(plan, left, right)
|
||||||
|
&& left.start < right.end
|
||||||
|
&& left.end > right.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compactActivationCopies(plan, keeperRef) {
|
||||||
|
const keeper = assignmentEntryForRef(plan, keeperRef);
|
||||||
|
if (!keeper) return false;
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
|
const assignments = mechanic.assignments ?? [];
|
||||||
|
const next = assignments.filter(assignment => {
|
||||||
|
if (assignment === keeper.assignment) return true;
|
||||||
|
if (assignment.ability !== keeper.assignment.ability) return true;
|
||||||
|
|
||||||
|
const start = assignmentStartMs(mechanic, assignment);
|
||||||
|
const entry = {
|
||||||
|
mechanic,
|
||||||
|
assignment,
|
||||||
|
start,
|
||||||
|
end: start + assignmentWindowMs(assignment),
|
||||||
|
};
|
||||||
|
const remove = assignmentsOverlapActiveFrame(plan, keeper, entry);
|
||||||
|
if (remove) changed = true;
|
||||||
|
return !remove;
|
||||||
|
});
|
||||||
|
if (next.length !== assignments.length) mechanic.assignments = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeActivationGroup(plan, ref) {
|
||||||
|
const keeper = assignmentEntryForRef(plan, ref);
|
||||||
|
if (!keeper) return false;
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
|
const assignments = mechanic.assignments ?? [];
|
||||||
|
const next = assignments.filter(assignment => {
|
||||||
|
if (assignment.ability !== keeper.assignment.ability) return true;
|
||||||
|
const start = assignmentStartMs(mechanic, assignment);
|
||||||
|
const entry = {
|
||||||
|
mechanic,
|
||||||
|
assignment,
|
||||||
|
start,
|
||||||
|
end: start + assignmentWindowMs(assignment),
|
||||||
|
};
|
||||||
|
const remove = assignment === keeper.assignment || assignmentsOverlapActiveFrame(plan, keeper, entry);
|
||||||
|
if (remove) changed = true;
|
||||||
|
return !remove;
|
||||||
|
});
|
||||||
|
if (next.length !== assignments.length) mechanic.assignments = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeActivationCopies(plan) {
|
||||||
|
const entries = [];
|
||||||
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
|
for (const assignment of mechanic.assignments ?? []) {
|
||||||
|
const start = assignmentStartMs(mechanic, assignment);
|
||||||
|
entries.push({
|
||||||
|
mechanic,
|
||||||
|
assignment,
|
||||||
|
start,
|
||||||
|
end: start + assignmentWindowMs(assignment),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.sort((a, b) => a.start - b.start);
|
||||||
|
|
||||||
|
const keepers = [];
|
||||||
|
const removals = new Set();
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (keepers.some(keeper => assignmentsOverlapActiveFrame(plan, keeper, entry))) {
|
||||||
|
removals.add(entry.assignment);
|
||||||
|
} else {
|
||||||
|
keepers.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!removals.size) return false;
|
||||||
|
|
||||||
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
|
const assignments = mechanic.assignments ?? [];
|
||||||
|
const next = assignments.filter(assignment => !removals.has(assignment));
|
||||||
|
if (next.length !== assignments.length) mechanic.assignments = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function findNearestMechanic(plan, timestamp) {
|
function findNearestMechanic(plan, timestamp) {
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
const mechanics = visiblePlanMechanics(plan);
|
||||||
if (!mechanics.length) return null;
|
if (!mechanics.length) return null;
|
||||||
@ -708,7 +836,7 @@ function layoutBossActions(mechanics, duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function assignmentWindowMs(assignment) {
|
function assignmentWindowMs(assignment) {
|
||||||
return Math.max(assignmentCooldownSeconds(assignment), assignmentDurationSeconds(assignment)) * 1000;
|
return Math.max(1, assignmentDurationSeconds(assignment)) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameAssignmentRef(mechanic, assignment, ref) {
|
function sameAssignmentRef(mechanic, assignment, ref) {
|
||||||
@ -719,13 +847,10 @@ function sameAssignmentRef(mechanic, assignment, ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function assignmentOverlapsJob(plan, job, ability, timestamp, ignore = null, candidate = null) {
|
function assignmentOverlapsJob(plan, job, ability, timestamp, ignore = null, candidate = null) {
|
||||||
const candidateWindow = Math.max(
|
const candidateWindow = Math.max(candidate ? assignmentDurationSeconds(candidate) : 0, 1) * 1000;
|
||||||
candidate ? assignmentCooldownSeconds(candidate) : 0,
|
|
||||||
candidate ? assignmentDurationSeconds(candidate) : 0,
|
|
||||||
1
|
|
||||||
) * 1000;
|
|
||||||
const candidateStart = Math.max(0, timestamp);
|
const candidateStart = Math.max(0, timestamp);
|
||||||
const candidateEnd = candidateStart + candidateWindow;
|
const candidateEnd = candidateStart + candidateWindow;
|
||||||
|
const ignoredActivation = assignmentEntryForRef(plan, ignore);
|
||||||
|
|
||||||
for (const mechanic of visiblePlanMechanics(plan)) {
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
for (const assignment of mechanic.assignments ?? []) {
|
for (const assignment of mechanic.assignments ?? []) {
|
||||||
@ -735,6 +860,10 @@ function assignmentOverlapsJob(plan, job, ability, timestamp, ignore = null, can
|
|||||||
|
|
||||||
const start = assignmentStartMs(mechanic, assignment);
|
const start = assignmentStartMs(mechanic, assignment);
|
||||||
const end = start + assignmentWindowMs(assignment);
|
const end = start + assignmentWindowMs(assignment);
|
||||||
|
if (ignoredActivation) {
|
||||||
|
const entry = { mechanic, assignment, start, end };
|
||||||
|
if (assignmentsOverlapActiveFrame(plan, ignoredActivation, entry)) continue;
|
||||||
|
}
|
||||||
if (candidateStart < end && candidateEnd > start) return true;
|
if (candidateStart < end && candidateEnd > start) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -902,6 +1031,7 @@ function renderTimelineSettingsHtml(plan) {
|
|||||||
function refreshTimeline(planId) {
|
function refreshTimeline(planId) {
|
||||||
const plan = getPlan(planId);
|
const plan = getPlan(planId);
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
|
if (normalizeActivationCopies(plan)) updatePlan(planId, { mechanics: plan.mechanics });
|
||||||
const timeline = document.getElementById('planner-timeline');
|
const timeline = document.getElementById('planner-timeline');
|
||||||
const settings = document.getElementById('timeline-settings');
|
const settings = document.getElementById('timeline-settings');
|
||||||
if (timeline) timeline.innerHTML = renderTimelineHtml(plan);
|
if (timeline) timeline.innerHTML = renderTimelineHtml(plan);
|
||||||
@ -928,6 +1058,7 @@ function setTimelineAssignmentJob(planId, mechanicId, ability, job, nextJob) {
|
|||||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||||
const assignment = mechanic?.assignments?.find(a => a.ability === ability && (a.job ?? '') === job);
|
const assignment = mechanic?.assignments?.find(a => a.ability === ability && (a.job ?? '') === job);
|
||||||
if (!assignment) return;
|
if (!assignment) return;
|
||||||
|
compactActivationCopies(plan, { mechanicId, ability, job });
|
||||||
const timestamp = assignmentStartMs(mechanic, assignment);
|
const timestamp = assignmentStartMs(mechanic, assignment);
|
||||||
if (assignmentOverlapsJob(plan, nextJob, ability, timestamp, { mechanicId, ability, job }, assignment)) return;
|
if (assignmentOverlapsJob(plan, nextJob, ability, timestamp, { mechanicId, ability, job }, assignment)) return;
|
||||||
assignment.job = nextJob;
|
assignment.job = nextJob;
|
||||||
@ -939,9 +1070,7 @@ function setTimelineAssignmentJob(planId, mechanicId, ability, job, nextJob) {
|
|||||||
function removeTimelineAssignment(planId, mechanicId, ability, job) {
|
function removeTimelineAssignment(planId, mechanicId, ability, job) {
|
||||||
const plan = getPlan(planId);
|
const plan = getPlan(planId);
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
if (!removeActivationGroup(plan, { mechanicId, ability, job })) return;
|
||||||
if (!mechanic) return;
|
|
||||||
mechanic.assignments = (mechanic.assignments ?? []).filter(a => !(a.ability === ability && (a.job ?? '') === job));
|
|
||||||
if (selectedTimelineAssignment?.mechanicId === mechanicId && selectedTimelineAssignment?.ability === ability && selectedTimelineAssignment?.job === job) {
|
if (selectedTimelineAssignment?.mechanicId === mechanicId && selectedTimelineAssignment?.ability === ability && selectedTimelineAssignment?.job === job) {
|
||||||
selectedTimelineAssignment = null;
|
selectedTimelineAssignment = null;
|
||||||
}
|
}
|
||||||
@ -1021,6 +1150,7 @@ function updateTimelineAssignmentPosition(planId, mechanicId, ability, job, rowJ
|
|||||||
const assignment = mechanic?.assignments?.find(a => a.ability === ability && (a.job ?? '') === job);
|
const assignment = mechanic?.assignments?.find(a => a.ability === ability && (a.job ?? '') === job);
|
||||||
if (!assignment) return;
|
if (!assignment) return;
|
||||||
if (!jobCanUseAbility(rowJob, ability)) return;
|
if (!jobCanUseAbility(rowJob, ability)) return;
|
||||||
|
compactActivationCopies(plan, { mechanicId, ability, job });
|
||||||
const nextTimestamp = Math.max(0, Math.round(timestamp));
|
const nextTimestamp = Math.max(0, Math.round(timestamp));
|
||||||
if (assignmentOverlapsJob(plan, rowJob, ability, nextTimestamp, { mechanicId, ability, job }, assignment)) return;
|
if (assignmentOverlapsJob(plan, rowJob, ability, nextTimestamp, { mechanicId, ability, job }, assignment)) return;
|
||||||
assignment.timestamp = nextTimestamp;
|
assignment.timestamp = nextTimestamp;
|
||||||
@ -1296,6 +1426,7 @@ function initTimeline(planId) {
|
|||||||
function refreshMechanicList(planId, includeTimeline = true) {
|
function refreshMechanicList(planId, includeTimeline = true) {
|
||||||
const plan = getPlan(planId);
|
const plan = getPlan(planId);
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
|
if (normalizeActivationCopies(plan)) updatePlan(planId, { mechanics: plan.mechanics });
|
||||||
const el = document.getElementById('mechanic-list');
|
const el = document.getElementById('mechanic-list');
|
||||||
if (el) el.innerHTML = renderMechanicListHtml(plan);
|
if (el) el.innerHTML = renderMechanicListHtml(plan);
|
||||||
if (includeTimeline) refreshTimeline(planId);
|
if (includeTimeline) refreshTimeline(planId);
|
||||||
@ -1372,11 +1503,19 @@ function renderInfoPanel(plan) {
|
|||||||
function removeAssignment(planId, mechanicId, abilityName, job = null) {
|
function removeAssignment(planId, mechanicId, abilityName, job = null) {
|
||||||
const plan = getPlan(planId);
|
const plan = getPlan(planId);
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
let removed = false;
|
||||||
if (!mechanic) return;
|
if (job === null) {
|
||||||
mechanic.assignments = mechanic.assignments.filter(a =>
|
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||||
a.ability !== abilityName || (job !== null && (a.job ?? '') !== job)
|
const jobs = [...new Set((mechanic?.assignments ?? [])
|
||||||
);
|
.filter(a => a.ability === abilityName)
|
||||||
|
.map(a => a.job ?? ''))];
|
||||||
|
jobs.forEach(assignmentJob => {
|
||||||
|
removed = removeActivationGroup(plan, { mechanicId, ability: abilityName, job: assignmentJob }) || removed;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
removed = removeActivationGroup(plan, { mechanicId, ability: abilityName, job });
|
||||||
|
}
|
||||||
|
if (!removed) return;
|
||||||
updatePlan(planId, { mechanics: plan.mechanics });
|
updatePlan(planId, { mechanics: plan.mechanics });
|
||||||
refreshMechanicList(planId);
|
refreshMechanicList(planId);
|
||||||
if (abilityModalMechanicId === mechanicId) renderAbilityModalContent();
|
if (abilityModalMechanicId === mechanicId) renderAbilityModalContent();
|
||||||
@ -1941,15 +2080,25 @@ function toggleAbilityAssignment(abilityName, job, buffType) {
|
|||||||
|
|
||||||
const idx = mechanic.assignments.findIndex(a => a.ability === abilityName);
|
const idx = mechanic.assignments.findIndex(a => a.ability === abilityName);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
if (mechanic.assignments[idx].job === job) {
|
const assignment = mechanic.assignments[idx];
|
||||||
mechanic.assignments.splice(idx, 1);
|
if (assignment.job === job) {
|
||||||
} else {
|
removeActivationGroup(plan, {
|
||||||
if (assignmentOverlapsJob(plan, job, abilityName, assignmentStartMs(mechanic, mechanic.assignments[idx]), {
|
|
||||||
mechanicId: mechanic.id,
|
mechanicId: mechanic.id,
|
||||||
ability: abilityName,
|
ability: abilityName,
|
||||||
job: mechanic.assignments[idx].job ?? '',
|
job: assignment.job ?? '',
|
||||||
}, mechanic.assignments[idx])) return;
|
});
|
||||||
mechanic.assignments[idx].job = job;
|
} else {
|
||||||
|
compactActivationCopies(plan, {
|
||||||
|
mechanicId: mechanic.id,
|
||||||
|
ability: abilityName,
|
||||||
|
job: assignment.job ?? '',
|
||||||
|
});
|
||||||
|
if (assignmentOverlapsJob(plan, job, abilityName, assignmentStartMs(mechanic, assignment), {
|
||||||
|
mechanicId: mechanic.id,
|
||||||
|
ability: abilityName,
|
||||||
|
job: assignment.job ?? '',
|
||||||
|
}, assignment)) return;
|
||||||
|
assignment.job = job;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const assignment = {
|
const assignment = {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const ACTION_SOURCE_URL = 'https://ff14.akurosiakamo.de/extras/json/xivapi_data/
|
|||||||
|
|
||||||
$rootDir = dirname(__DIR__);
|
$rootDir = dirname(__DIR__);
|
||||||
$mitigationSource = $rootDir . '/api/analysis.php';
|
$mitigationSource = $rootDir . '/api/analysis.php';
|
||||||
|
$plannerDataSource = $rootDir . '/js/ffxiv-data.js';
|
||||||
$outputFile = $rootDir . '/assets/jsons/Action.json';
|
$outputFile = $rootDir . '/assets/jsons/Action.json';
|
||||||
|
|
||||||
function fail(string $message, int $code = 1): void
|
function fail(string $message, int $code = 1): void
|
||||||
@ -80,7 +81,100 @@ function extract_constant_array_literal(string $php, string $constantName): stri
|
|||||||
fail('Could not parse array literal for ' . $constantName);
|
fail('Could not parse array literal for ' . $constantName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function read_mitigation_action_ids(string $sourceFile): array
|
function extract_js_const_object_literal(string $js, string $constantName): string
|
||||||
|
{
|
||||||
|
$needle = 'const ' . $constantName . ' =';
|
||||||
|
$start = strpos($js, $needle);
|
||||||
|
|
||||||
|
if ($start === false) {
|
||||||
|
fail('Could not find const ' . $constantName . ' in js/ffxiv-data.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
$objectStart = strpos($js, '{', $start);
|
||||||
|
if ($objectStart === false) {
|
||||||
|
fail('Could not find object literal for ' . $constantName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$depth = 0;
|
||||||
|
$length = strlen($js);
|
||||||
|
$inString = false;
|
||||||
|
$stringQuote = '';
|
||||||
|
$escaped = false;
|
||||||
|
|
||||||
|
for ($i = $objectStart; $i < $length; $i++) {
|
||||||
|
$char = $js[$i];
|
||||||
|
|
||||||
|
if ($inString) {
|
||||||
|
if ($escaped) {
|
||||||
|
$escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '\\') {
|
||||||
|
$escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === $stringQuote) {
|
||||||
|
$inString = false;
|
||||||
|
$stringQuote = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '\'' || $char === '"' || $char === '`') {
|
||||||
|
$inString = true;
|
||||||
|
$stringQuote = $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '{') {
|
||||||
|
$depth++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '}') {
|
||||||
|
$depth--;
|
||||||
|
|
||||||
|
if ($depth === 0) {
|
||||||
|
return substr($js, $objectStart, $i - $objectStart + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail('Could not parse object literal for ' . $constantName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read_planner_ability_names(string $sourceFile): array
|
||||||
|
{
|
||||||
|
if (!is_file($sourceFile)) {
|
||||||
|
fail('Missing planner data source file: ' . $sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$js = file_get_contents($sourceFile);
|
||||||
|
if ($js === false) {
|
||||||
|
fail('Could not read planner data source file: ' . $sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$literal = extract_js_const_object_literal($js, 'JOB_ABILITIES');
|
||||||
|
if (!preg_match_all('/\bname\s*:\s*([\'"])((?:\\\\.|(?!\1).)*)\1/s', $literal, $matches)) {
|
||||||
|
fail('No abilities found in JOB_ABILITIES');
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
foreach ($matches[2] as $rawName) {
|
||||||
|
$name = stripcslashes($rawName);
|
||||||
|
if ($name !== '') {
|
||||||
|
$names[$name] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($names, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
return array_keys($names);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read_mitigation_action_ids(string $sourceFile, array $abilityNames): array
|
||||||
{
|
{
|
||||||
if (!is_file($sourceFile)) {
|
if (!is_file($sourceFile)) {
|
||||||
fail('Missing mitigation source file: ' . $sourceFile);
|
fail('Missing mitigation source file: ' . $sourceFile);
|
||||||
@ -98,8 +192,15 @@ function read_mitigation_action_ids(string $sourceFile): array
|
|||||||
fail('MITIGATION_ABILITIES did not parse as an array');
|
fail('MITIGATION_ABILITIES did not parse as an array');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$wantedNames = array_fill_keys($abilityNames, true);
|
||||||
$ids = [];
|
$ids = [];
|
||||||
foreach ($abilities as $name => $ability) {
|
foreach ($wantedNames as $name => $_) {
|
||||||
|
if (!isset($abilities[$name])) {
|
||||||
|
fwrite(STDERR, 'Planner ability missing in MITIGATION_ABILITIES: ' . $name . PHP_EOL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ability = $abilities[$name];
|
||||||
$id = (int)($ability['extraAbilityGameID'] ?? 0);
|
$id = (int)($ability['extraAbilityGameID'] ?? 0);
|
||||||
if ($id <= 0) {
|
if ($id <= 0) {
|
||||||
fwrite(STDERR, 'Skipping mitigation without extraAbilityGameID: ' . $name . PHP_EOL);
|
fwrite(STDERR, 'Skipping mitigation without extraAbilityGameID: ' . $name . PHP_EOL);
|
||||||
@ -110,7 +211,7 @@ function read_mitigation_action_ids(string $sourceFile): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$ids) {
|
if (!$ids) {
|
||||||
fail('No extraAbilityGameID values found in MITIGATION_ABILITIES');
|
fail('No extraAbilityGameID values found for abilities from js/ffxiv-data.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
ksort($ids, SORT_NUMERIC);
|
ksort($ids, SORT_NUMERIC);
|
||||||
@ -188,7 +289,8 @@ function action_field(array $action, string $field): ?int
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$actionIds = read_mitigation_action_ids($mitigationSource);
|
$plannerAbilityNames = read_planner_ability_names($plannerDataSource);
|
||||||
|
$actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames);
|
||||||
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
||||||
|
|
||||||
$json = download_url(ACTION_SOURCE_URL);
|
$json = download_url(ACTION_SOURCE_URL);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user