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);
|
return Math.round(hps.reduce((s, v) => s + v, 0) / hps.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function simulateDrMultiplier(mechanic) {
|
function simulateDrMultiplier(mechanic, assignments = mechanic.assignments ?? []) {
|
||||||
let mult = 1;
|
let mult = 1;
|
||||||
for (const a of mechanic.assignments ?? []) {
|
for (const a of assignments) {
|
||||||
if (a.buffType === 'shield') continue;
|
if (a.buffType === 'shield') continue;
|
||||||
mult *= (1 - (ABILITY_DR[a.ability] ?? 0));
|
mult *= (1 - (ABILITY_DR[a.ability] ?? 0));
|
||||||
}
|
}
|
||||||
return mult;
|
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) {
|
function renderMechanicListHtml(plan) {
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
const mechanics = visiblePlanMechanics(plan);
|
||||||
if (mechanics.length === 0) {
|
if (mechanics.length === 0) {
|
||||||
@ -462,7 +482,8 @@ function renderMechanicListHtml(plan) {
|
|||||||
const avgHp = avgNonTankMaxHp(plan);
|
const avgHp = avgNonTankMaxHp(plan);
|
||||||
|
|
||||||
return mechanics.map(m => {
|
return mechanics.map(m => {
|
||||||
const sorted = sortedAssignments(m.assignments);
|
const effective = effectiveAssignmentsForMechanic(plan, m);
|
||||||
|
const sorted = sortedAssignments(effective);
|
||||||
const assignHtml = sorted.length === 0
|
const assignHtml = sorted.length === 0
|
||||||
? '<span class="mechanic-no-assign">Keine Zuweisung</span>'
|
? '<span class="mechanic-no-assign">Keine Zuweisung</span>'
|
||||||
: sorted.map(a => {
|
: 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}"` : ''}>
|
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="">` : ''}
|
${icon ? `<img class="badge-icon" src="${escHtml(icon)}" alt="">` : ''}
|
||||||
${label}
|
${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>`;
|
</span>`;
|
||||||
const hintHtml = suggestions.map(s =>
|
const hintHtml = suggestions.map(s =>
|
||||||
`<span class="badge-equiv-hint">→ ${escHtml(s.ability)} (${escHtml(s.job)})?</span>`
|
`<span class="badge-equiv-hint">→ ${escHtml(s.ability)} (${escHtml(s.job)})?</span>`
|
||||||
@ -492,10 +513,10 @@ function renderMechanicListHtml(plan) {
|
|||||||
: badgeHtml;
|
: badgeHtml;
|
||||||
}).join('');
|
}).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 shieldVal = (plan.shieldK ?? 0) * 1000;
|
||||||
const mitigFull = Math.max(0, drOnly - shieldVal);
|
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 hasShield = shieldVal > 0;
|
||||||
const drOnlyCls = avgHp ? (drOnly <= avgHp ? 'mechanic-mitig--ok' : 'mechanic-mitig--risk') : '';
|
const drOnlyCls = avgHp ? (drOnly <= avgHp ? 'mechanic-mitig--ok' : 'mechanic-mitig--risk') : '';
|
||||||
const fullCls = avgHp ? (mitigFull <= 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;
|
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) {
|
function findNearestMechanic(plan, timestamp) {
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
const mechanics = visiblePlanMechanics(plan);
|
||||||
if (!mechanics.length) return null;
|
if (!mechanics.length) return null;
|
||||||
@ -714,13 +762,17 @@ function renderTimelineHtml(plan) {
|
|||||||
|
|
||||||
const playerRows = rows.map(row => {
|
const playerRows = rows.map(row => {
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
for (const m of mechanics) {
|
const rowAssignments = canonicalAssignmentActivations(plan).filter(entry => {
|
||||||
for (const a of sortedAssignments(m.assignments ?? [])) {
|
const assignment = entry.assignment;
|
||||||
if (a.ability !== row.ability) continue;
|
if (assignment.ability !== row.ability) return false;
|
||||||
const assignedJob = a.job ?? '';
|
const assignedJob = assignment.job ?? '';
|
||||||
if (assignedJob && assignedJob !== row.job) continue;
|
return !assignedJob || assignedJob === row.job;
|
||||||
const start = Number.isFinite(Number(a.timestamp)) ? Number(a.timestamp) : m.timestamp;
|
});
|
||||||
const durationSec = assignmentDurationSeconds(a);
|
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 cooldownSec = assignmentCooldownSeconds(a);
|
||||||
const left = Math.max(0, Math.min(100, (start / duration) * 100));
|
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));
|
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>
|
<span>${escHtml(abilityLabel)}</span>
|
||||||
</button>
|
</button>
|
||||||
`);
|
`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const icon = MITIG_ICONS[row.ability] ?? '';
|
const icon = MITIG_ICONS[row.ability] ?? '';
|
||||||
const abilityDisplayName = plan.mitigationNames?.[row.ability] ?? 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 === 'durationSeconds') assignment.durationSeconds = Math.max(1, Math.round(Number(value)));
|
||||||
if (field === 'cooldownSeconds') assignment.cooldownSeconds = Math.max(0, Math.round(Number(value)));
|
if (field === 'cooldownSeconds') assignment.cooldownSeconds = Math.max(0, Math.round(Number(value)));
|
||||||
updatePlan(planId, { mechanics: plan.mechanics });
|
updatePlan(planId, { mechanics: plan.mechanics });
|
||||||
refreshTimeline(planId);
|
refreshMechanicList(planId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTimelineAssignmentJob(planId, mechanicId, ability, job, nextJob) {
|
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);
|
const plan = getPlan(planId);
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||||
if (!mechanic) return;
|
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 });
|
updatePlan(planId, { mechanics: plan.mechanics });
|
||||||
refreshMechanicList(planId);
|
refreshMechanicList(planId);
|
||||||
if (abilityModalMechanicId === mechanicId) renderAbilityModalContent();
|
if (abilityModalMechanicId === mechanicId) renderAbilityModalContent();
|
||||||
@ -1160,7 +1213,7 @@ function initMechanicClicks(planId) {
|
|||||||
const removeBtn = e.target.closest('.badge-remove');
|
const removeBtn = e.target.closest('.badge-remove');
|
||||||
if (removeBtn) {
|
if (removeBtn) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability);
|
removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability, removeBtn.dataset.job ?? null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const deleteBtn = e.target.closest('.mechanic-delete-btn');
|
const deleteBtn = e.target.closest('.mechanic-delete-btn');
|
||||||
@ -1179,7 +1232,7 @@ function initMechanicClicks(planId) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const removeBtn = badge.querySelector('.badge-remove');
|
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