Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sim/core/consumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func registerNonCombatPotion(agent Agent, elixirOrFlaskId int32) *Spell {
Label: item.Name,
ActionID: actionID,
Duration: item.BuffDuration,
BuildPhase: CharacterBuildPhaseBuffs,
BuildPhase: CharacterBuildPhaseConsumes,
})

registerExclusiveNonCombatPotionBuff(item.Type, aura, potionStats)
Expand Down
50 changes: 43 additions & 7 deletions ui/core/components/suggest_reforges_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1708,13 +1708,7 @@ export class ReforgeOptimizer {
}

if (stat == Stat.StatHasteRating || stat == Stat.StatMasteryRating || stat == Stat.StatSpirit) {
this.player.getAmplificationTrinkets().forEach(trinket => {
const randPropPoints = this.sim.db.getItemEffectRandPropPoints(trinket.ilvl)?.randPropPoints;
if (!randPropPoints) return;
const statScalingCoeff = 0.00176999997;
const buffValue = 1 + (statScalingCoeff * randPropPoints) / 100;
amount *= buffValue;
});
amount *= this.player.getTotalAmplificationTrinketStatModifier();
}

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

private getOptimisticUnitStatUpperBound(unitStat: UnitStat, variables: YalpsVariables): number {
const statKey = unitStat.getKey();
const maxByGroup = new Map<string, number>();

for (const [variableKey, coefficients] of variables.entries()) {
const splitKey = variableKey.split('_');
const groupKey = splitKey.length > 2 ? `${splitKey[0]}_${splitKey[1]}` : (splitKey[0] ?? variableKey);
const statContribution = coefficients.get(statKey) || 0;
const currentMax = maxByGroup.get(groupKey) ?? Number.NEGATIVE_INFINITY;

if (statContribution > currentMax) {
maxByGroup.set(groupKey, statContribution);
}
}

let upperBound = 0;
for (const contribution of maxByGroup.values()) {
upperBound += Math.max(0, contribution);
}

return upperBound;
}

checkCaps(
solution: LPSolution,
reforgeCaps: Stats,
Expand Down Expand Up @@ -1997,6 +2014,25 @@ export class ReforgeOptimizer {
const statName = unitStat.getKey();
const currentValue = reforgeStatContribution.getUnitStat(unitStat);

const firstBreakpoint = nextSoftCap.breakpoints[0];
if (firstBreakpoint !== undefined && currentValue < firstBreakpoint && !updatedConstraints.has(statName)) {
const optimisticUpperBound = this.getOptimisticUnitStatUpperBound(unitStat, variables);

if (optimisticUpperBound >= firstBreakpoint) {
updatedConstraints.set(statName, greaterEq(firstBreakpoint));
anyCapsExceeded = true;
if (isDevMode()) console.log('Soft cap target not met, enforcing floor for: %s', statName);
break;
} else if (isDevMode()) {
console.log(
'Soft cap target is unreachable for %s (needed: %s, optimistic max: %s); skipping floor constraint.',
statName,
firstBreakpoint,
optimisticUpperBound,
);
}
}

let idx = 0;
for (const breakpoint of nextSoftCap.breakpoints) {
if (currentValue > breakpoint) {
Expand Down
13 changes: 13 additions & 0 deletions ui/core/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1682,4 +1682,17 @@ export class Player<SpecType extends Spec> {
})
.filter((i): i is EquippedItem => !!i);
}

getTotalAmplificationTrinketStatModifier() {
const trinkets = this.getAmplificationTrinkets();
let totalModifier = 1;
for (const trinket of trinkets) {
const randPropPoints = this.sim.db.getItemEffectRandPropPoints(trinket.ilvl)?.randPropPoints;
if (!randPropPoints) continue;
const statScalingCoeff = 0.00176999997;
const buffValue = 1 + (statScalingCoeff * randPropPoints) / 100;
totalModifier *= buffValue;
}
return totalModifier;
}
}
27 changes: 27 additions & 0 deletions ui/paladin/protection/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, {
})(),
softCapBreakpoints: (() => {
return [
StatCap.fromPseudoStat(PseudoStat.PseudoStatMeleeHastePercent, {
breakpoints: [50],
capType: StatCapType.TypeSoftCap,
postCapEPs: [1.1 * Mechanics.HASTE_RATING_PER_HASTE_PERCENT],
}),
StatCap.fromStat(Stat.StatExpertiseRating, {
breakpoints: [7.5 * 4 * Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION, 15 * 4 * Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION],
capType: StatCapType.TypeSoftCap,
Expand Down Expand Up @@ -231,7 +236,29 @@ export class ProtectionPaladinSimUI extends IndividualSimUI<Spec.SpecProtectionP
softCapToModify.postCapEPs = P2ExpertisePostCapEPs;
}
}
if (softCap.unitStat.equalsPseudoStat(PseudoStat.PseudoStatMeleeHastePercent) && softCapToModify) {
const raidBuffs = player.getRaid()?.getBuffs()!;
const hasMeleeHaste = [
raidBuffs.unholyAura,
raidBuffs.cacklingHowl,
raidBuffs.serpentsSwiftness,
raidBuffs.swiftbladesCunning,
raidBuffs.unleashedRage,
].some(Boolean);

let targetPercent = 50;
if (hasMeleeHaste) {
targetPercent += 15;
}

softCapToModify.breakpoints = [targetPercent];
softCapToModify.postCapEPs = [
((epWeights.getStat(Stat.StatCritRating) - 0.02) / player.getTotalAmplificationTrinketStatModifier()) *
Mechanics.HASTE_RATING_PER_HASTE_PERCENT,
];
}
});

return softCaps;
},
});
Expand Down
Loading