Skip to content

Commit ec1d930

Browse files
authored
Merge pull request #1385 from wowsims/feature/paladin
Feature/paladin
2 parents d1ecb4f + 5c28273 commit ec1d930

3 files changed

Lines changed: 83 additions & 7 deletions

File tree

ui/core/components/suggest_reforges_action.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,13 +1708,7 @@ export class ReforgeOptimizer {
17081708
}
17091709

17101710
if (stat == Stat.StatHasteRating || stat == Stat.StatMasteryRating || stat == Stat.StatSpirit) {
1711-
this.player.getAmplificationTrinkets().forEach(trinket => {
1712-
const randPropPoints = this.sim.db.getItemEffectRandPropPoints(trinket.ilvl)?.randPropPoints;
1713-
if (!randPropPoints) return;
1714-
const statScalingCoeff = 0.00176999997;
1715-
const buffValue = 1 + (statScalingCoeff * randPropPoints) / 100;
1716-
amount *= buffValue;
1717-
});
1711+
amount *= this.player.getTotalAmplificationTrinketStatModifier();
17181712
}
17191713

17201714
// Handle Spirit to Spell Hit conversion for hybrid casters separately from standard dependencies
@@ -1939,6 +1933,29 @@ export class ReforgeOptimizer {
19391933
return updatedGear;
19401934
}
19411935

1936+
private getOptimisticUnitStatUpperBound(unitStat: UnitStat, variables: YalpsVariables): number {
1937+
const statKey = unitStat.getKey();
1938+
const maxByGroup = new Map<string, number>();
1939+
1940+
for (const [variableKey, coefficients] of variables.entries()) {
1941+
const splitKey = variableKey.split('_');
1942+
const groupKey = splitKey.length > 2 ? `${splitKey[0]}_${splitKey[1]}` : (splitKey[0] ?? variableKey);
1943+
const statContribution = coefficients.get(statKey) || 0;
1944+
const currentMax = maxByGroup.get(groupKey) ?? Number.NEGATIVE_INFINITY;
1945+
1946+
if (statContribution > currentMax) {
1947+
maxByGroup.set(groupKey, statContribution);
1948+
}
1949+
}
1950+
1951+
let upperBound = 0;
1952+
for (const contribution of maxByGroup.values()) {
1953+
upperBound += Math.max(0, contribution);
1954+
}
1955+
1956+
return upperBound;
1957+
}
1958+
19421959
checkCaps(
19431960
solution: LPSolution,
19441961
reforgeCaps: Stats,
@@ -1997,6 +2014,25 @@ export class ReforgeOptimizer {
19972014
const statName = unitStat.getKey();
19982015
const currentValue = reforgeStatContribution.getUnitStat(unitStat);
19992016

2017+
const firstBreakpoint = nextSoftCap.breakpoints[0];
2018+
if (firstBreakpoint !== undefined && currentValue < firstBreakpoint && !updatedConstraints.has(statName)) {
2019+
const optimisticUpperBound = this.getOptimisticUnitStatUpperBound(unitStat, variables);
2020+
2021+
if (optimisticUpperBound >= firstBreakpoint) {
2022+
updatedConstraints.set(statName, greaterEq(firstBreakpoint));
2023+
anyCapsExceeded = true;
2024+
if (isDevMode()) console.log('Soft cap target not met, enforcing floor for: %s', statName);
2025+
break;
2026+
} else if (isDevMode()) {
2027+
console.log(
2028+
'Soft cap target is unreachable for %s (needed: %s, optimistic max: %s); skipping floor constraint.',
2029+
statName,
2030+
firstBreakpoint,
2031+
optimisticUpperBound,
2032+
);
2033+
}
2034+
}
2035+
20002036
let idx = 0;
20012037
for (const breakpoint of nextSoftCap.breakpoints) {
20022038
if (currentValue > breakpoint) {

ui/core/player.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,4 +1682,17 @@ export class Player<SpecType extends Spec> {
16821682
})
16831683
.filter((i): i is EquippedItem => !!i);
16841684
}
1685+
1686+
getTotalAmplificationTrinketStatModifier() {
1687+
const trinkets = this.getAmplificationTrinkets();
1688+
let totalModifier = 1;
1689+
for (const trinket of trinkets) {
1690+
const randPropPoints = this.sim.db.getItemEffectRandPropPoints(trinket.ilvl)?.randPropPoints;
1691+
if (!randPropPoints) continue;
1692+
const statScalingCoeff = 0.00176999997;
1693+
const buffValue = 1 + (statScalingCoeff * randPropPoints) / 100;
1694+
totalModifier *= buffValue;
1695+
}
1696+
return totalModifier;
1697+
}
16851698
}

ui/paladin/protection/sim.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, {
109109
})(),
110110
softCapBreakpoints: (() => {
111111
return [
112+
StatCap.fromPseudoStat(PseudoStat.PseudoStatMeleeHastePercent, {
113+
breakpoints: [50],
114+
capType: StatCapType.TypeSoftCap,
115+
postCapEPs: [1.1 * Mechanics.HASTE_RATING_PER_HASTE_PERCENT],
116+
}),
112117
StatCap.fromStat(Stat.StatExpertiseRating, {
113118
breakpoints: [7.5 * 4 * Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION, 15 * 4 * Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION],
114119
capType: StatCapType.TypeSoftCap,
@@ -231,7 +236,29 @@ export class ProtectionPaladinSimUI extends IndividualSimUI<Spec.SpecProtectionP
231236
softCapToModify.postCapEPs = P2ExpertisePostCapEPs;
232237
}
233238
}
239+
if (softCap.unitStat.equalsPseudoStat(PseudoStat.PseudoStatMeleeHastePercent) && softCapToModify) {
240+
const raidBuffs = player.getRaid()?.getBuffs()!;
241+
const hasMeleeHaste = [
242+
raidBuffs.unholyAura,
243+
raidBuffs.cacklingHowl,
244+
raidBuffs.serpentsSwiftness,
245+
raidBuffs.swiftbladesCunning,
246+
raidBuffs.unleashedRage,
247+
].some(Boolean);
248+
249+
let targetPercent = 50;
250+
if (hasMeleeHaste) {
251+
targetPercent += 15;
252+
}
253+
254+
softCapToModify.breakpoints = [targetPercent];
255+
softCapToModify.postCapEPs = [
256+
((epWeights.getStat(Stat.StatCritRating) - 0.02) / player.getTotalAmplificationTrinketStatModifier()) *
257+
Mechanics.HASTE_RATING_PER_HASTE_PERCENT,
258+
];
259+
}
234260
});
261+
235262
return softCaps;
236263
},
237264
});

0 commit comments

Comments
 (0)