Add optimization target toggle
This commit is contained in:
@@ -22,6 +22,7 @@ const PROFILE_IDS = Object.freeze({ LIFESTEAL: "lifesteal", REGEN: "regen" });
|
||||
const COMBAT_STYLES = Object.freeze({ MELEE: "melee", RANGED: "ranged", SKILL: "skill" });
|
||||
const PIECE_TYPES = Object.freeze({ ITEM: "item", MOUNT: "mount", PET: "pet", SKILL: "skill" });
|
||||
const REPLACEMENT_TARGETS = Object.freeze({ MOUNT: "mount", PET: "pet" });
|
||||
const OPTIMIZATION_TARGETS = Object.freeze({ DAMAGE: "damage", LIFESTEAL_REGEN: "lifestealRegen" });
|
||||
|
||||
const PROFILES = {
|
||||
[PROFILE_IDS.LIFESTEAL]: {
|
||||
@@ -73,6 +74,7 @@ function createDefaultState() {
|
||||
return {
|
||||
profile: PROFILE_IDS.LIFESTEAL,
|
||||
combatStyle: COMBAT_STYLES.MELEE,
|
||||
optimizationTarget: OPTIMIZATION_TARGETS.DAMAGE,
|
||||
recommendationCount: 8,
|
||||
pieces: slots.map((slot, index) => createDefaultPiece(slot, index)),
|
||||
newItem: createDefaultNewItem(),
|
||||
@@ -128,6 +130,7 @@ function normalizeState(candidate) {
|
||||
...candidate,
|
||||
profile,
|
||||
combatStyle: Object.values(COMBAT_STYLES).includes(candidate?.combatStyle) ? candidate.combatStyle : COMBAT_STYLES.MELEE,
|
||||
optimizationTarget: Object.values(OPTIMIZATION_TARGETS).includes(candidate?.optimizationTarget) ? candidate.optimizationTarget : OPTIMIZATION_TARGETS.DAMAGE,
|
||||
recommendationCount: Math.min(20, Math.max(3, Number(candidate?.recommendationCount || 8))),
|
||||
newItem: normalizeNewItem(candidate?.newItem),
|
||||
pieces: fallback.pieces.map((piece, index) => normalizePiece(piece, candidate?.pieces?.[index], index)),
|
||||
@@ -208,6 +211,7 @@ function renderControls() {
|
||||
$("#profileSelect").innerHTML = Object.entries(PROFILES).map(([id, profile]) => `<option value="${id}">${profile.label}</option>`).join("");
|
||||
$("#profileSelect").value = state.profile;
|
||||
$("#combatStyle").value = state.combatStyle;
|
||||
$("#optimizationTarget").value = state.optimizationTarget;
|
||||
$("#recommendationCount").value = state.recommendationCount;
|
||||
}
|
||||
|
||||
@@ -661,8 +665,9 @@ function renderEffectTotals(totals, currentDps, currentSurvivability) {
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function getDamageOptimizationEffectIds(combatStyle) {
|
||||
function getDamageOptimizationEffectIds(combatStyle, optimizationTarget = OPTIMIZATION_TARGETS.DAMAGE) {
|
||||
const ids = ["critChance", "critDamage", "doubleChance", "damage", "attackSpeed"];
|
||||
if (optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN) ids.push("lifesteal");
|
||||
if (combatStyle === COMBAT_STYLES.RANGED) ids.push("rangedDamage");
|
||||
else if (combatStyle === COMBAT_STYLES.SKILL) ids.push("skillDamage", "skillCooldown");
|
||||
else ids.push("meleeDamage");
|
||||
@@ -689,9 +694,24 @@ 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])) };
|
||||
function calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget) {
|
||||
const dps = calculateDps(candidateTotals, combatStyle);
|
||||
return optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN
|
||||
? calculateRegenDetails(candidateTotals, dps).lifesteal
|
||||
: dps;
|
||||
}
|
||||
|
||||
function getOptimizationMetricLabel(optimizationTarget) {
|
||||
return optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN ? "Optimal lifesteal regen" : "Optimal DPS";
|
||||
}
|
||||
|
||||
function findOptimalDamageSetup(totals, effectCount, combatStyle, optimizationTarget) {
|
||||
const effectIds = getDamageOptimizationEffectIds(combatStyle, optimizationTarget);
|
||||
const emptyTotals = { ...totals, effects: createEmptyEffectTotals() };
|
||||
let best = {
|
||||
metric: calculateOptimizationMetric(emptyTotals, combatStyle, optimizationTarget),
|
||||
counts: Object.fromEntries(effectIds.map((id) => [id, 0])),
|
||||
};
|
||||
const counts = Object.fromEntries(effectIds.map((id) => [id, 0]));
|
||||
|
||||
function visit(effectIndex, remaining) {
|
||||
@@ -700,8 +720,9 @@ function findOptimalDamageSetup(totals, effectCount, combatStyle) {
|
||||
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 } };
|
||||
const candidateTotals = { baseDamage: totals.baseDamage, baseHealth: totals.baseHealth, effects };
|
||||
const metric = calculateOptimizationMetric(candidateTotals, combatStyle, optimizationTarget);
|
||||
if (metric > best.metric) best = { metric, counts: { ...counts } };
|
||||
counts[effectId] = 0;
|
||||
return;
|
||||
}
|
||||
@@ -736,12 +757,16 @@ function renderEffectComparisonRows(optimumCounts, currentSummary) {
|
||||
|
||||
function renderOptimalDamageSetup(totals, currentDps) {
|
||||
const effectCount = countActiveEffects();
|
||||
const optimum = findOptimalDamageSetup(totals, effectCount, state.combatStyle);
|
||||
const delta = percentChange(currentDps, optimum.dps);
|
||||
const optimum = findOptimalDamageSetup(totals, effectCount, state.combatStyle, state.optimizationTarget);
|
||||
const currentMetric = state.optimizationTarget === OPTIMIZATION_TARGETS.LIFESTEAL_REGEN
|
||||
? calculateRegenDetails(totals, currentDps).lifesteal
|
||||
: currentDps;
|
||||
const delta = percentChange(currentMetric, optimum.metric);
|
||||
const currentEffectSummary = summarizeCurrentEffects();
|
||||
|
||||
$("#optimumEffectCount").textContent = `${effectCount} effect${effectCount === 1 ? "" : "s"}`;
|
||||
$("#optimumDps").textContent = fmt(optimum.dps);
|
||||
$("#optimumMetricLabel").textContent = getOptimizationMetricLabel(state.optimizationTarget);
|
||||
$("#optimumDps").textContent = fmt(optimum.metric);
|
||||
$("#optimumDpsDelta").textContent = formatDelta(delta, "%");
|
||||
$("#optimumDpsDelta").className = deltaClass(delta);
|
||||
renderEffectComparisonRows(effectCount ? optimum.counts : {}, currentEffectSummary);
|
||||
@@ -927,6 +952,10 @@ function bindEvents() {
|
||||
state.combatStyle = event.target.value;
|
||||
updateAnalysis();
|
||||
});
|
||||
$("#optimizationTarget").addEventListener("change", (event) => {
|
||||
state.optimizationTarget = event.target.value;
|
||||
updateAnalysis();
|
||||
});
|
||||
$("#recommendationCount").addEventListener("input", (event) => {
|
||||
state.recommendationCount = Math.min(20, Math.max(3, Number(event.target.value || 8)));
|
||||
updateAnalysis();
|
||||
|
||||
Reference in New Issue
Block a user