From 7b44a15af28e5ca034f7f88d42bb3ebe554813b2 Mon Sep 17 00:00:00 2001 From: Saji Date: Sun, 14 Dec 2025 15:41:38 -0500 Subject: [PATCH 1/2] Add reforge optimizer setting for preferring a reforge to hit instead of to expertise when both options are present. Enable this by default for all mage specs --- .../components/suggest_reforges_action.tsx | 28 +++++++++++++++++-- ui/mage/arcane/sim.tsx | 1 + ui/mage/fire/sim.tsx | 1 + ui/mage/frost/sim.ts | 1 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ui/core/components/suggest_reforges_action.tsx b/ui/core/components/suggest_reforges_action.tsx index 1541b039b4..d08c4c7380 100644 --- a/ui/core/components/suggest_reforges_action.tsx +++ b/ui/core/components/suggest_reforges_action.tsx @@ -108,6 +108,9 @@ export type ReforgeOptimizerOptions = { // Sets the default stat to be the highest for relative stat cap calculations // Defaults to Any defaultRelativeStatCap?: Stat | null; + // For casters: prefer Hit Rating over Expertise Rating + // This filters out Expertise reforges when Hit reforges are available for the same item + preferHitOverExpertise?: boolean; }; // Used to force a particular proc from trinkets like Matrix Restabilizer and Apparatus of Khaz'goroth. @@ -245,6 +248,7 @@ export class ReforgeOptimizer { protected statTooltips: StatTooltipContent = {}; protected additionalSoftCapTooltipInformation: StatTooltipContent = {}; protected statSelectionPresets: ReforgeOptimizerOptions['statSelectionPresets']; + protected preferHitOverExpertise: boolean; protected includeGems = false; protected includeEOTBPGemSocket = false; protected freezeItemSlots = false; @@ -291,6 +295,7 @@ export class ReforgeOptimizer { this._statCaps = this.defaults.statCaps || new Stats(); this.enableBreakpointLimits = !!options?.enableBreakpointLimits; this.relativeStatCapStat = options?.defaultRelativeStatCap ?? -1; + this.preferHitOverExpertise = options?.preferHitOverExpertise ?? false; // Pre-warm the worker pool getReforgeWorkerPool().warmUp(); @@ -1349,12 +1354,31 @@ export class ReforgeOptimizer { } const scaledItem = item.withDynamicStats(); + const availableReforges = this.player.getAvailableReforgings(scaledItem); - for (const reforgeData of this.player.getAvailableReforgings(scaledItem)) { + // Filter reforges based on preferences + let reforgesToUse = availableReforges.filter(reforgeData => { if (!epStats.includes(reforgeData.toStat) && reforgeData.toStat != Stat.StatExpertiseRating) { - continue; + return false; + } + + // For casters: prefer Hit over Expertise + // If the item doesn't have Hit or Expertise natively, remove Expertise as a reforge option + if (this.preferHitOverExpertise && reforgeData.toStat === Stat.StatExpertiseRating) { + const itemStats = scaledItem.calcStats(slot); + const hasNativeHit = itemStats.getStat(Stat.StatHitRating) > 0; + const hasNativeExpertise = itemStats.getStat(Stat.StatExpertiseRating) > 0; + + // If item has neither Hit nor Expertise natively, don't allow reforging to Expertise + if (!hasNativeHit && !hasNativeExpertise) { + return false; + } } + return true; + }); + + for (const reforgeData of reforgesToUse) { const variableKey = `${slot}_${reforgeData.id}`; const coefficients = new Map(); coefficients.set(ItemSlot[slot], 1); diff --git a/ui/mage/arcane/sim.tsx b/ui/mage/arcane/sim.tsx index a0d94fb561..3177cf6f4d 100644 --- a/ui/mage/arcane/sim.tsx +++ b/ui/mage/arcane/sim.tsx @@ -172,6 +172,7 @@ export class ArcaneMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: statSelectionPresets, enableBreakpointLimits: true, + preferHitOverExpertise: true, getEPDefaults: player => { const avgIlvl = player.getGear().getAverageItemLevel(false); if (avgIlvl >= 525) { diff --git a/ui/mage/fire/sim.tsx b/ui/mage/fire/sim.tsx index aad5f73dc3..bce8647fb4 100755 --- a/ui/mage/fire/sim.tsx +++ b/ui/mage/fire/sim.tsx @@ -309,6 +309,7 @@ export class FireMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: statSelectionPresets, enableBreakpointLimits: true, + preferHitOverExpertise: true, // updateSoftCaps: softCaps => { // const raidBuffs = player.getRaid()?.getBuffs(); // const hasBL = !!raidBuffs?.bloodlust; diff --git a/ui/mage/frost/sim.ts b/ui/mage/frost/sim.ts index 2d4a978d37..d96a635690 100644 --- a/ui/mage/frost/sim.ts +++ b/ui/mage/frost/sim.ts @@ -188,6 +188,7 @@ export class FrostMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: [MAGE_BREAKPOINTS], enableBreakpointLimits: true, + preferHitOverExpertise: true, getEPDefaults: player => { const avgIlvl = player.getGear().getAverageItemLevel(false); if (avgIlvl >= 517) { From ceb6b53d80924e2709da0dd2805259d648722e33 Mon Sep 17 00:00:00 2001 From: Saji Date: Sat, 20 Dec 2025 00:35:34 -0500 Subject: [PATCH 2/2] Remove preferHitOverExpertise option, auto-detect caster specs - Removed preferHitOverExpertise boolean parameter from ReforgeOptimizer - Replaced with automatic caster spec detection - Casters (Mage, Warlock, Priest, Balance/Resto Druid, Ele/Resto Shaman, Holy Paladin, Mistweaver Monk) now automatically prefer Hit over Expertise - Removed preferHitOverExpertise: true from all mage spec UIs --- .../components/suggest_reforges_action.tsx | 21 ++++++++++++------- ui/mage/arcane/sim.tsx | 1 - ui/mage/fire/sim.tsx | 1 - ui/mage/frost/sim.ts | 1 - 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ui/core/components/suggest_reforges_action.tsx b/ui/core/components/suggest_reforges_action.tsx index d08c4c7380..969083bd85 100644 --- a/ui/core/components/suggest_reforges_action.tsx +++ b/ui/core/components/suggest_reforges_action.tsx @@ -108,9 +108,6 @@ export type ReforgeOptimizerOptions = { // Sets the default stat to be the highest for relative stat cap calculations // Defaults to Any defaultRelativeStatCap?: Stat | null; - // For casters: prefer Hit Rating over Expertise Rating - // This filters out Expertise reforges when Hit reforges are available for the same item - preferHitOverExpertise?: boolean; }; // Used to force a particular proc from trinkets like Matrix Restabilizer and Apparatus of Khaz'goroth. @@ -248,7 +245,6 @@ export class ReforgeOptimizer { protected statTooltips: StatTooltipContent = {}; protected additionalSoftCapTooltipInformation: StatTooltipContent = {}; protected statSelectionPresets: ReforgeOptimizerOptions['statSelectionPresets']; - protected preferHitOverExpertise: boolean; protected includeGems = false; protected includeEOTBPGemSocket = false; protected freezeItemSlots = false; @@ -295,7 +291,6 @@ export class ReforgeOptimizer { this._statCaps = this.defaults.statCaps || new Stats(); this.enableBreakpointLimits = !!options?.enableBreakpointLimits; this.relativeStatCapStat = options?.defaultRelativeStatCap ?? -1; - this.preferHitOverExpertise = options?.preferHitOverExpertise ?? false; // Pre-warm the worker pool getReforgeWorkerPool().warmUp(); @@ -1364,11 +1359,21 @@ export class ReforgeOptimizer { // For casters: prefer Hit over Expertise // If the item doesn't have Hit or Expertise natively, remove Expertise as a reforge option - if (this.preferHitOverExpertise && reforgeData.toStat === Stat.StatExpertiseRating) { - const itemStats = scaledItem.calcStats(slot); + const isCaster = this.playerClass === Class.ClassMage || + this.playerClass === Class.ClassWarlock || + this.playerClass === Class.ClassPriest || + this.player.getSpec() === Spec.SpecBalanceDruid || + this.player.getSpec() === Spec.SpecRestorationDruid || + this.player.getSpec() === Spec.SpecElementalShaman || + this.player.getSpec() === Spec.SpecRestorationShaman || + this.player.getSpec() === Spec.SpecHolyPaladin || + this.player.getSpec() === Spec.SpecMistweaverMonk; + + if (isCaster && reforgeData.toStat === Stat.StatExpertiseRating) { + const itemStats = scaledItem.calcStats(); const hasNativeHit = itemStats.getStat(Stat.StatHitRating) > 0; const hasNativeExpertise = itemStats.getStat(Stat.StatExpertiseRating) > 0; - + // If item has neither Hit nor Expertise natively, don't allow reforging to Expertise if (!hasNativeHit && !hasNativeExpertise) { return false; diff --git a/ui/mage/arcane/sim.tsx b/ui/mage/arcane/sim.tsx index 3177cf6f4d..a0d94fb561 100644 --- a/ui/mage/arcane/sim.tsx +++ b/ui/mage/arcane/sim.tsx @@ -172,7 +172,6 @@ export class ArcaneMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: statSelectionPresets, enableBreakpointLimits: true, - preferHitOverExpertise: true, getEPDefaults: player => { const avgIlvl = player.getGear().getAverageItemLevel(false); if (avgIlvl >= 525) { diff --git a/ui/mage/fire/sim.tsx b/ui/mage/fire/sim.tsx index bce8647fb4..aad5f73dc3 100755 --- a/ui/mage/fire/sim.tsx +++ b/ui/mage/fire/sim.tsx @@ -309,7 +309,6 @@ export class FireMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: statSelectionPresets, enableBreakpointLimits: true, - preferHitOverExpertise: true, // updateSoftCaps: softCaps => { // const raidBuffs = player.getRaid()?.getBuffs(); // const hasBL = !!raidBuffs?.bloodlust; diff --git a/ui/mage/frost/sim.ts b/ui/mage/frost/sim.ts index d96a635690..2d4a978d37 100644 --- a/ui/mage/frost/sim.ts +++ b/ui/mage/frost/sim.ts @@ -188,7 +188,6 @@ export class FrostMageSimUI extends IndividualSimUI { this.reforger = new ReforgeOptimizer(this, { statSelectionPresets: [MAGE_BREAKPOINTS], enableBreakpointLimits: true, - preferHitOverExpertise: true, getEPDefaults: player => { const avgIlvl = player.getGear().getAverageItemLevel(false); if (avgIlvl >= 517) {