Add maximum damage effect optimizer
This commit is contained in:
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user