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];
|
||||
|
||||
+13
@@ -89,6 +89,19 @@
|
||||
<div id="effectTotals" class="effect-totals"></div>
|
||||
</section>
|
||||
|
||||
<section class="card optimum-card" aria-labelledby="optimum-heading">
|
||||
<div class="gear-title-row">
|
||||
<h2 id="optimum-heading">Maximum damage setup</h2>
|
||||
<span id="optimumEffectCount" class="piece-score"></span>
|
||||
</div>
|
||||
<p class="muted-copy">Best max-roll damage effect mix using the same number of filled effect slots as your current build.</p>
|
||||
<div class="optimum-results">
|
||||
<div><span>Optimal DPS</span><strong id="optimumDps">0</strong></div>
|
||||
<div><span>Difference vs current</span><strong id="optimumDpsDelta">0</strong></div>
|
||||
</div>
|
||||
<p id="optimumSuggestion" class="optimum-suggestion"></p>
|
||||
</section>
|
||||
|
||||
<section class="card new-item-card" aria-labelledby="new-item-heading">
|
||||
<div class="gear-title-row">
|
||||
<h2 id="new-item-heading">New item comparison</h2>
|
||||
|
||||
+30
-1
@@ -180,6 +180,7 @@ h3 { margin-bottom: 0; }
|
||||
.recommendation-list li { padding-left: 0.25rem; }
|
||||
.recommendation-list strong { color: var(--accent); }
|
||||
.recommendation-list small { display: block; color: var(--muted); margin-top: 0.2rem; }
|
||||
.muted-copy { color: var(--muted); margin-bottom: 0.85rem; }
|
||||
|
||||
.effect-totals {
|
||||
display: grid;
|
||||
@@ -255,6 +256,34 @@ h3 { margin-bottom: 0; }
|
||||
.delta-positive { color: #86efac; }
|
||||
.delta-neutral { color: var(--muted); }
|
||||
.delta-negative { color: #f87171; }
|
||||
.optimum-card {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.optimum-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.optimum-results > div {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.9rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.optimum-results span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.optimum-results strong { font-size: 1.35rem; }
|
||||
.optimum-suggestion {
|
||||
margin: 0;
|
||||
color: var(--accent);
|
||||
font-weight: 800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.swap-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -270,6 +299,6 @@ h3 { margin-bottom: 0; }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
main, .hero { width: min(100% - 1rem, 1440px); }
|
||||
.summary-grid, .inline-fields, .effect-row { grid-template-columns: 1fr; }
|
||||
.summary-grid, .inline-fields, .effect-row, .optimum-results { grid-template-columns: 1fr; }
|
||||
.save-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user