forked from xziino/ff14-mitigator
adjustments to skill behaviour
This commit is contained in:
parent
4ec929ebb7
commit
d0f54049e6
@ -435,15 +435,35 @@ function avgNonTankMaxHp(plan) {
|
||||
return Math.round(hps.reduce((s, v) => s + v, 0) / hps.length);
|
||||
}
|
||||
|
||||
function simulateDrMultiplier(mechanic) {
|
||||
function simulateDrMultiplier(mechanic, assignments = mechanic.assignments ?? []) {
|
||||
let mult = 1;
|
||||
for (const a of mechanic.assignments ?? []) {
|
||||
for (const a of assignments) {
|
||||
if (a.buffType === 'shield') continue;
|
||||
mult *= (1 - (ABILITY_DR[a.ability] ?? 0));
|
||||
}
|
||||
return mult;
|
||||
}
|
||||
|
||||
function effectiveAssignmentsForMechanic(plan, targetMechanic) {
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const entry of canonicalAssignmentActivations(plan)) {
|
||||
if (targetMechanic.timestamp < entry.start || targetMechanic.timestamp > entry.end) continue;
|
||||
const assignment = entry.assignment;
|
||||
const key = `${assignment.ability}::${assignment.job ?? ''}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
result.push({
|
||||
...assignment,
|
||||
sourceMechanicId: entry.mechanic.id,
|
||||
sourceStart: entry.start,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function renderMechanicListHtml(plan) {
|
||||
const mechanics = visiblePlanMechanics(plan);
|
||||
if (mechanics.length === 0) {
|
||||
@ -462,7 +482,8 @@ function renderMechanicListHtml(plan) {
|
||||
const avgHp = avgNonTankMaxHp(plan);
|
||||
|
||||
return mechanics.map(m => {
|
||||
const sorted = sortedAssignments(m.assignments);
|
||||
const effective = effectiveAssignmentsForMechanic(plan, m);
|
||||
const sorted = sortedAssignments(effective);
|
||||
const assignHtml = sorted.length === 0
|
||||
? '<span class="mechanic-no-assign">Keine Zuweisung</span>'
|
||||
: sorted.map(a => {
|
||||
@ -478,7 +499,7 @@ function renderMechanicListHtml(plan) {
|
||||
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>
|
||||
<button class="badge-remove" data-mechanic-id="${escHtml(a.sourceMechanicId ?? m.id)}" data-ability="${escHtml(a.ability)}" data-job="${escHtml(a.job ?? '')}" title="Entfernen">×</button>
|
||||
</span>`;
|
||||
const hintHtml = suggestions.map(s =>
|
||||
`<span class="badge-equiv-hint">→ ${escHtml(s.ability)} (${escHtml(s.job)})?</span>`
|
||||
@ -492,10 +513,10 @@ function renderMechanicListHtml(plan) {
|
||||
: badgeHtml;
|
||||
}).join('');
|
||||
|
||||
const drOnly = m.unmitigatedDamage ? Math.round(m.unmitigatedDamage * simulateDrMultiplier(m)) : 0;
|
||||
const drOnly = m.unmitigatedDamage ? Math.round(m.unmitigatedDamage * simulateDrMultiplier(m, effective)) : 0;
|
||||
const shieldVal = (plan.shieldK ?? 0) * 1000;
|
||||
const mitigFull = Math.max(0, drOnly - shieldVal);
|
||||
const hasDrAssign = (m.assignments ?? []).some(a => a.buffType !== 'shield' && (ABILITY_DR[a.ability] ?? 0) > 0);
|
||||
const hasDrAssign = effective.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') : '';
|
||||
@ -620,6 +641,33 @@ function assignmentStartMs(mechanic, assignment) {
|
||||
return Number.isFinite(Number(assignment?.timestamp)) ? Number(assignment.timestamp) : mechanic.timestamp;
|
||||
}
|
||||
|
||||
function canonicalAssignmentActivations(plan) {
|
||||
const entries = [];
|
||||
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||
for (const assignment of mechanic.assignments ?? []) {
|
||||
const start = assignmentStartMs(mechanic, assignment);
|
||||
const durationSec = assignmentDurationSeconds(assignment);
|
||||
entries.push({
|
||||
mechanic,
|
||||
assignment,
|
||||
start,
|
||||
durationSec,
|
||||
end: start + durationSec * 1000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
entries.sort((a, b) => a.start - b.start);
|
||||
const activeUntilBySkill = new Map();
|
||||
return entries.filter(entry => {
|
||||
const key = `${entry.assignment.ability}::${entry.assignment.job ?? ''}`;
|
||||
const activeUntil = activeUntilBySkill.get(key) ?? -Infinity;
|
||||
if (entry.start < activeUntil) return false;
|
||||
activeUntilBySkill.set(key, entry.end);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function findNearestMechanic(plan, timestamp) {
|
||||
const mechanics = visiblePlanMechanics(plan);
|
||||
if (!mechanics.length) return null;
|
||||
@ -714,13 +762,17 @@ function renderTimelineHtml(plan) {
|
||||
|
||||
const playerRows = rows.map(row => {
|
||||
const blocks = [];
|
||||
for (const m of mechanics) {
|
||||
for (const a of sortedAssignments(m.assignments ?? [])) {
|
||||
if (a.ability !== row.ability) continue;
|
||||
const assignedJob = a.job ?? '';
|
||||
if (assignedJob && assignedJob !== row.job) continue;
|
||||
const start = Number.isFinite(Number(a.timestamp)) ? Number(a.timestamp) : m.timestamp;
|
||||
const durationSec = assignmentDurationSeconds(a);
|
||||
const rowAssignments = canonicalAssignmentActivations(plan).filter(entry => {
|
||||
const assignment = entry.assignment;
|
||||
if (assignment.ability !== row.ability) return false;
|
||||
const assignedJob = assignment.job ?? '';
|
||||
return !assignedJob || assignedJob === row.job;
|
||||
});
|
||||
for (const item of rowAssignments) {
|
||||
const m = item.mechanic;
|
||||
const a = item.assignment;
|
||||
const start = item.start;
|
||||
const durationSec = item.durationSec;
|
||||
const cooldownSec = assignmentCooldownSeconds(a);
|
||||
const left = Math.max(0, Math.min(100, (start / duration) * 100));
|
||||
const widthPct = Math.max(1.2, Math.min(100 - left, (durationSec * 1000 / duration) * 100));
|
||||
@ -746,7 +798,6 @@ function renderTimelineHtml(plan) {
|
||||
<span>${escHtml(abilityLabel)}</span>
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
}
|
||||
const icon = MITIG_ICONS[row.ability] ?? '';
|
||||
const abilityDisplayName = plan.mitigationNames?.[row.ability] ?? row.ability;
|
||||
@ -838,7 +889,7 @@ function setTimelineAssignmentField(planId, mechanicId, ability, job, field, val
|
||||
if (field === 'durationSeconds') assignment.durationSeconds = Math.max(1, Math.round(Number(value)));
|
||||
if (field === 'cooldownSeconds') assignment.cooldownSeconds = Math.max(0, Math.round(Number(value)));
|
||||
updatePlan(planId, { mechanics: plan.mechanics });
|
||||
refreshTimeline(planId);
|
||||
refreshMechanicList(planId);
|
||||
}
|
||||
|
||||
function setTimelineAssignmentJob(planId, mechanicId, ability, job, nextJob) {
|
||||
@ -1133,12 +1184,14 @@ function renderInfoPanel(plan) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeAssignment(planId, mechanicId, abilityName) {
|
||||
function removeAssignment(planId, mechanicId, abilityName, job = null) {
|
||||
const plan = getPlan(planId);
|
||||
if (!plan) return;
|
||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||
if (!mechanic) return;
|
||||
mechanic.assignments = mechanic.assignments.filter(a => a.ability !== abilityName);
|
||||
mechanic.assignments = mechanic.assignments.filter(a =>
|
||||
a.ability !== abilityName || (job !== null && (a.job ?? '') !== job)
|
||||
);
|
||||
updatePlan(planId, { mechanics: plan.mechanics });
|
||||
refreshMechanicList(planId);
|
||||
if (abilityModalMechanicId === mechanicId) renderAbilityModalContent();
|
||||
@ -1160,7 +1213,7 @@ function initMechanicClicks(planId) {
|
||||
const removeBtn = e.target.closest('.badge-remove');
|
||||
if (removeBtn) {
|
||||
e.stopPropagation();
|
||||
removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability);
|
||||
removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability, removeBtn.dataset.job ?? null);
|
||||
return;
|
||||
}
|
||||
const deleteBtn = e.target.closest('.mechanic-delete-btn');
|
||||
@ -1179,7 +1232,7 @@ function initMechanicClicks(planId) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const removeBtn = badge.querySelector('.badge-remove');
|
||||
if (removeBtn) removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability);
|
||||
if (removeBtn) removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability, removeBtn.dataset.job ?? null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user