Add maximum damage effect optimizer

This commit is contained in:
Jan Bader
2026-06-20 23:51:25 +02:00
parent 0e539e6bc0
commit 196e6e3630
3 changed files with 112 additions and 1 deletions
+69
View File
@@ -341,6 +341,7 @@ function updateAnalysis() {
updateEffectImpactHints(totals, dps);
renderEffectTotals(totals, dps, survivability);
renderOptimalDamageSetup(totals, dps);
renderSacrifices(pieceScores);
renderReplacementComparison(totals, dps, regenDetails.total, pieceScores);
saveDraft();
@@ -660,6 +661,74 @@ function renderEffectTotals(totals, currentDps, currentSurvivability) {
`).join("");
}
function getDamageOptimizationEffectIds(combatStyle) {
const ids = ["critChance", "critDamage", "doubleChance", "damage", "attackSpeed"];
if (combatStyle === COMBAT_STYLES.RANGED) ids.push("rangedDamage");
else if (combatStyle === COMBAT_STYLES.SKILL) ids.push("skillDamage", "skillCooldown");
else ids.push("meleeDamage");
return ids;
}
function countActiveEffects() {
return state.pieces.reduce((count, piece) => count + (piece.effects || []).filter((effect) => effect.type).length, 0);
}
function createEmptyEffectTotals() {
return Object.fromEntries(EFFECTS.map((effect) => [effect.id, 0]));
}
function findOptimalDamageSetup(totals, effectCount, combatStyle) {
const effectIds = getDamageOptimizationEffectIds(combatStyle);
let best = { dps: calculateDps({ ...totals, effects: createEmptyEffectTotals() }, combatStyle), counts: Object.fromEntries(effectIds.map((id) => [id, 0])) };
const counts = Object.fromEntries(effectIds.map((id) => [id, 0]));
function visit(effectIndex, remaining) {
const effectId = effectIds[effectIndex];
if (effectIndex === effectIds.length - 1) {
counts[effectId] = remaining;
const effects = createEmptyEffectTotals();
for (const id of effectIds) effects[id] = counts[id] * effectById(id).cap;
const dps = calculateDps({ baseDamage: totals.baseDamage, baseHealth: totals.baseHealth, effects }, combatStyle);
if (dps > best.dps) best = { dps, counts: { ...counts } };
counts[effectId] = 0;
return;
}
for (let count = 0; count <= remaining; count += 1) {
counts[effectId] = count;
visit(effectIndex + 1, remaining - count);
}
counts[effectId] = 0;
}
visit(0, effectCount);
return best;
}
function formatOptimalSuggestion(counts) {
return Object.entries(counts)
.filter(([, count]) => count > 0)
.map(([effectId, count]) => {
const effect = effectById(effectId);
return `${count}x ${effect.cap}% ${effect.label.toLowerCase()}`;
})
.join(", ");
}
function renderOptimalDamageSetup(totals, currentDps) {
const effectCount = countActiveEffects();
const optimum = findOptimalDamageSetup(totals, effectCount, state.combatStyle);
const delta = percentChange(currentDps, optimum.dps);
$("#optimumEffectCount").textContent = `${effectCount} effect${effectCount === 1 ? "" : "s"}`;
$("#optimumDps").textContent = fmt(optimum.dps);
$("#optimumDpsDelta").textContent = formatDelta(delta, "%");
$("#optimumDpsDelta").className = deltaClass(delta);
$("#optimumSuggestion").textContent = effectCount
? formatOptimalSuggestion(optimum.counts)
: "Add effects to your current build to see an optimized max-roll damage mix.";
}
function updateEffectImpactHints(totals, currentDps) {
document.querySelectorAll(".gear-card").forEach((card, pieceIndex) => {
const piece = state.pieces[pieceIndex];