Normalize mixed damage lifesteal weighting
This commit is contained in:
@@ -694,24 +694,28 @@ function createEmptyEffectTotals() {
|
|||||||
return Object.fromEntries(EFFECTS.map((effect) => [effect.id, 0]));
|
return Object.fromEntries(EFFECTS.map((effect) => [effect.id, 0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget) {
|
function calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget, metricContext = {}) {
|
||||||
const dps = calculateDps(candidateTotals, combatStyle);
|
const dps = calculateDps(candidateTotals, combatStyle);
|
||||||
const lifestealRegen = calculateRegenDetails(candidateTotals, dps).lifesteal;
|
const lifestealRegen = calculateRegenDetails(candidateTotals, dps).lifesteal;
|
||||||
if (optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN) return lifestealRegen;
|
if (optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN) return lifestealRegen;
|
||||||
|
if (optimizationTarget === OPTIMIZATION_TARGETS.DAMAGE_LIFESTEAL_MIX) {
|
||||||
|
const damageScore = metricContext.bestDamageDps > 0 ? dps / metricContext.bestDamageDps : 0;
|
||||||
|
const lifestealScore = metricContext.bestLifestealRegen > 0 ? lifestealRegen / metricContext.bestLifestealRegen : 0;
|
||||||
|
return (2 * damageScore) + lifestealScore;
|
||||||
|
}
|
||||||
return dps;
|
return dps;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptimizationMetricLabel(optimizationTarget) {
|
function getOptimizationMetricLabel(optimizationTarget) {
|
||||||
if (optimizationTarget === OPTIMIZATION_TARGETS.DAMAGE_LIFESTEAL_MIX) return "Optimal DPS with 2:1 mix";
|
if (optimizationTarget === OPTIMIZATION_TARGETS.DAMAGE_LIFESTEAL_MIX) return "Optimal weighted score";
|
||||||
return optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN ? "Optimal lifesteal regen" : "Optimal DPS";
|
return optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN ? "Optimal lifesteal regen" : "Optimal DPS";
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOptimalDamageSetup(totals, effectCount, combatStyle, optimizationTarget) {
|
function findOptimalDamageSetup(totals, effectCount, combatStyle, optimizationTarget, metricContext = {}) {
|
||||||
const effectIds = getDamageOptimizationEffectIds(combatStyle, optimizationTarget);
|
const effectIds = getDamageOptimizationEffectIds(combatStyle, optimizationTarget);
|
||||||
const emptyTotals = { ...totals, effects: createEmptyEffectTotals() };
|
const emptyTotals = { ...totals, effects: createEmptyEffectTotals() };
|
||||||
const requiredLifestealCount = optimizationTarget === OPTIMIZATION_TARGETS.DAMAGE_LIFESTEAL_MIX ? Math.round(effectCount / 3) : null;
|
|
||||||
let best = {
|
let best = {
|
||||||
metric: requiredLifestealCount ? -Infinity : calculateOptimizationMetric(emptyTotals, combatStyle, optimizationTarget),
|
metric: calculateOptimizationMetric(emptyTotals, combatStyle, optimizationTarget, metricContext),
|
||||||
counts: Object.fromEntries(effectIds.map((id) => [id, 0])),
|
counts: Object.fromEntries(effectIds.map((id) => [id, 0])),
|
||||||
};
|
};
|
||||||
const counts = Object.fromEntries(effectIds.map((id) => [id, 0]));
|
const counts = Object.fromEntries(effectIds.map((id) => [id, 0]));
|
||||||
@@ -720,14 +724,10 @@ function findOptimalDamageSetup(totals, effectCount, combatStyle, optimizationTa
|
|||||||
const effectId = effectIds[effectIndex];
|
const effectId = effectIds[effectIndex];
|
||||||
if (effectIndex === effectIds.length - 1) {
|
if (effectIndex === effectIds.length - 1) {
|
||||||
counts[effectId] = remaining;
|
counts[effectId] = remaining;
|
||||||
if (requiredLifestealCount !== null && counts.lifesteal !== requiredLifestealCount) {
|
|
||||||
counts[effectId] = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const effects = createEmptyEffectTotals();
|
const effects = createEmptyEffectTotals();
|
||||||
for (const id of effectIds) effects[id] = counts[id] * effectById(id).cap;
|
for (const id of effectIds) effects[id] = counts[id] * effectById(id).cap;
|
||||||
const candidateTotals = { baseDamage: totals.baseDamage, baseHealth: totals.baseHealth, effects };
|
const candidateTotals = { baseDamage: totals.baseDamage, baseHealth: totals.baseHealth, effects };
|
||||||
const metric = calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget);
|
const metric = calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget, metricContext);
|
||||||
if (metric > best.metric) best = { metric, counts: { ...counts } };
|
if (metric > best.metric) best = { metric, counts: { ...counts } };
|
||||||
counts[effectId] = 0;
|
counts[effectId] = 0;
|
||||||
return;
|
return;
|
||||||
@@ -763,8 +763,9 @@ function renderEffectComparisonRows(optimumCounts, currentSummary) {
|
|||||||
|
|
||||||
function renderOptimalDamageSetup(totals, currentDps) {
|
function renderOptimalDamageSetup(totals, currentDps) {
|
||||||
const effectCount = countActiveEffects();
|
const effectCount = countActiveEffects();
|
||||||
const optimum = findOptimalDamageSetup(totals, effectCount, state.combatStyle, state.optimizationTarget);
|
const metricContext = getOptimizationMetricContext(totals, effectCount, state.combatStyle, state.optimizationTarget);
|
||||||
const currentMetric = calculateOptimizationMetric(totals, state.combatStyle, state.optimizationTarget);
|
const optimum = findOptimalDamageSetup(totals, effectCount, state.combatStyle, state.optimizationTarget, metricContext);
|
||||||
|
const currentMetric = calculateOptimizationMetric(totals, state.combatStyle, state.optimizationTarget, metricContext);
|
||||||
const delta = percentChange(currentMetric, optimum.metric);
|
const delta = percentChange(currentMetric, optimum.metric);
|
||||||
const currentEffectSummary = summarizeCurrentEffects();
|
const currentEffectSummary = summarizeCurrentEffects();
|
||||||
|
|
||||||
@@ -776,6 +777,14 @@ function renderOptimalDamageSetup(totals, currentDps) {
|
|||||||
renderEffectComparisonRows(effectCount ? optimum.counts : {}, currentEffectSummary);
|
renderEffectComparisonRows(effectCount ? optimum.counts : {}, currentEffectSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptimizationMetricContext(totals, effectCount, combatStyle, optimizationTarget) {
|
||||||
|
if (optimizationTarget !== OPTIMIZATION_TARGETS.DAMAGE_LIFESTEAL_MIX || effectCount <= 0) return {};
|
||||||
|
return {
|
||||||
|
bestDamageDps: findOptimalDamageSetup(totals, effectCount, combatStyle, OPTIMIZATION_TARGETS.DAMAGE).metric,
|
||||||
|
bestLifestealRegen: findOptimalDamageSetup(totals, effectCount, combatStyle, OPTIMIZATION_TARGETS.LIFESTEAL_REGEN).metric,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function updateEffectImpactHints(totals, currentDps) {
|
function updateEffectImpactHints(totals, currentDps) {
|
||||||
document.querySelectorAll(".gear-card").forEach((card, pieceIndex) => {
|
document.querySelectorAll(".gear-card").forEach((card, pieceIndex) => {
|
||||||
const piece = state.pieces[pieceIndex];
|
const piece = state.pieces[pieceIndex];
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@
|
|||||||
<select id="optimizationTarget">
|
<select id="optimizationTarget">
|
||||||
<option value="damage">Raw damage</option>
|
<option value="damage">Raw damage</option>
|
||||||
<option value="lifestealRegen">Lifesteal regen</option>
|
<option value="lifestealRegen">Lifesteal regen</option>
|
||||||
<option value="damageLifestealMix">2x damage + 1x lifesteal</option>
|
<option value="damageLifestealMix">2:1 weighted damage + lifesteal</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user