From 9432b6a4cea507097ce85945325b8ae597ee627a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 16 Jun 2026 15:13:57 +0200 Subject: [PATCH] Show max-roll effect value in totals --- README.md | 4 ++++ app.js | 31 ++++++++++++++++++++++++++++--- styles.css | 15 ++++++++++----- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7aa16d7..e958acd 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ All additive bonuses are evaluated against the current total. For example, if th Each item effect row displays its actual current DPS contribution and the marginal DPS gain from the next `+1%` upgrade. +The effect totals panel also shows what adding one more maximum-roll effect would do right now. For example, if the build already has `+100% melee damage`, another max `+50% melee damage` changes the multiplier from `2.0x` to `2.5x`, so it displays `+25% DPS`. + +Defensive effect totals use an estimated survivability value based on health, block chance, health regen, and lifesteal. This is meant for comparison between effects, not as an exact combat simulator. + ## Strategy profile The first version includes an adjustable strategy profile: diff --git a/app.js b/app.js index 32fc24b..3652cfe 100644 --- a/app.js +++ b/app.js @@ -157,6 +157,7 @@ function updateAnalysis() { const profile = PROFILES[state.profile]; const totals = calculateTotals(); const dps = calculateDps(totals, state.combatStyle); + const survivability = calculateSurvivability(totals); const pieceScores = state.pieces.map((piece) => scorePiece(piece, profile, totals)); $("#totalDamage").textContent = fmt(totals.baseDamage); @@ -169,7 +170,7 @@ function updateAnalysis() { }); updateEffectImpactHints(totals, dps); - renderEffectTotals(totals.effects); + renderEffectTotals(totals, dps, survivability); renderUpgrades(profile, totals, dps); renderSacrifices(pieceScores); saveDraft(); @@ -202,6 +203,16 @@ function calculateDps(totals, combatStyle = "melee") { return baseDamage * globalDamage * styleDamage * attackSpeed * doubleHit * crit * cooldown; } +function calculateSurvivability(totals) { + const effects = totals.effects; + const baseHealth = Math.max(0, totals.baseHealth); + const health = baseHealth * (1 + effects.health / 100); + const blockMultiplier = 1 / Math.max(0.05, 1 - effects.blockChance / 100); + const regenMultiplier = 1 + effects.healthRegen / 100; + const lifestealMultiplier = 1 + effects.lifesteal / 100; + return health * blockMultiplier * regenMultiplier * lifestealMultiplier; +} + function getStyleDamageBonus(effects, combatStyle) { if (combatStyle === "ranged") return effects.rangedDamage; if (combatStyle === "skill") return effects.skillDamage; @@ -219,6 +230,16 @@ function cloneTotalsWithEffectDelta(totals, effectType, delta) { }; } +function describeMaxRollGain(effect, totals, currentDps, currentSurvivability) { + const withMaxRoll = cloneTotalsWithEffectDelta(totals, effect.id, effect.cap); + const dpsGain = percentChange(currentDps, calculateDps(withMaxRoll, state.combatStyle)); + const survivabilityGain = percentChange(currentSurvivability, calculateSurvivability(withMaxRoll)); + const parts = []; + if (Math.abs(dpsGain) >= 0.05) parts.push(`+${fmt(dpsGain)}% DPS`); + if (Math.abs(survivabilityGain) >= 0.05) parts.push(`+${fmt(survivabilityGain)}% survivability`); + return parts.length ? parts.join(" / ") : "no effect for current style"; +} + function percentChange(from, to) { if (from <= 0) return to > 0 ? 100 : 0; return ((to / from) - 1) * 100; @@ -253,9 +274,13 @@ function scorePiece(piece, profile, totals) { return { piece, score, reasons: [...new Set(reasons)] }; } -function renderEffectTotals(effectTotals) { +function renderEffectTotals(totals, currentDps, currentSurvivability) { $("#effectTotals").innerHTML = EFFECTS.map((effect) => ` -
${effect.label}${fmt(effectTotals[effect.id])}%
+
+ ${effect.label} + ${fmt(totals.effects[effect.id])}% + Max roll +${effect.cap}%: ${describeMaxRollGain(effect, totals, currentDps, currentSurvivability)} +
`).join(""); } diff --git a/styles.css b/styles.css index d86ff8b..c6b5917 100644 --- a/styles.css +++ b/styles.css @@ -152,18 +152,23 @@ h3 { margin-bottom: 0; } .effect-totals { display: grid; - grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0.75rem; } .effect-pill { - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: 1fr auto; gap: 1rem; + align-items: start; border: 1px solid var(--border); - border-radius: 999px; - padding: 0.55rem 0.8rem; + border-radius: 1rem; + padding: 0.75rem 0.85rem; background: rgba(255, 255, 255, 0.05); } +.effect-pill small { + grid-column: 1 / -1; + color: var(--muted); +} .section-heading { margin: 1.5rem 0 1rem; } .section-heading p { color: var(--muted); }