From e39088a2f6710486372d04adcede711c007fd964 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Wed, 8 Jan 2025 13:22:33 -0700 Subject: [PATCH 1/3] First pass at handedness crit cap for Classic --- ui/core/components/character_stats.tsx | 191 ++++++++++++++++--------- ui/core/player.ts | 86 ++++++++--- 2 files changed, 186 insertions(+), 91 deletions(-) diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index e9e53c7e0..4911e098a 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -3,7 +3,7 @@ import { ref } from 'tsx-vanilla'; import * as Mechanics from '../constants/mechanics.js'; import { Player } from '../player.js'; -import { PseudoStat, Spec, Stat } from '../proto/common.js'; +import { HandType, ItemSlot, PseudoStat, Spec, Stat, WeaponType } from '../proto/common.js'; import { Stats, UnitStat } from '../proto_utils/stats.js'; import { EventID, TypedEvent } from '../typed_event.js'; import { Component } from './component.js'; @@ -12,13 +12,7 @@ import { NumberPicker } from './number_picker'; export type StatMods = { talents?: Stats; buffs?: Stats }; const statGroups = new Map>([ - [ - 'Primary', - [ - UnitStat.fromStat(Stat.StatHealth), - UnitStat.fromStat(Stat.StatMana), - ], - ], + ['Primary', [UnitStat.fromStat(Stat.StatHealth), UnitStat.fromStat(Stat.StatMana)]], [ 'Attributes', [ @@ -27,7 +21,7 @@ const statGroups = new Map>([ UnitStat.fromStat(Stat.StatStamina), UnitStat.fromStat(Stat.StatIntellect), UnitStat.fromStat(Stat.StatSpirit), - ] + ], ], [ 'Physical', @@ -40,7 +34,7 @@ const statGroups = new Map>([ UnitStat.fromStat(Stat.StatMeleeCrit), UnitStat.fromStat(Stat.StatMeleeHaste), UnitStat.fromPseudoStat(PseudoStat.BonusPhysicalDamage), - ] + ], ], [ 'Spell', @@ -58,7 +52,7 @@ const statGroups = new Map>([ UnitStat.fromStat(Stat.StatSpellHaste), UnitStat.fromStat(Stat.StatSpellPenetration), UnitStat.fromStat(Stat.StatMP5), - ] + ], ], [ 'Defense', @@ -70,7 +64,7 @@ const statGroups = new Map>([ UnitStat.fromStat(Stat.StatParry), UnitStat.fromStat(Stat.StatBlock), UnitStat.fromStat(Stat.StatBlockValue), - ] + ], ], [ 'Resistance', @@ -80,13 +74,10 @@ const statGroups = new Map>([ UnitStat.fromStat(Stat.StatFrostResistance), UnitStat.fromStat(Stat.StatNatureResistance), UnitStat.fromStat(Stat.StatShadowResistance), - ] - ], - [ - 'Misc', - [] + ], ], -]) + ['Misc', []], +]); export class CharacterStats extends Component { readonly stats: Array; @@ -248,45 +239,45 @@ export class CharacterStats extends Component {
Axes - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatAxesSkill)} /{' '} - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedAxesSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatAxesSkill)} /{' '} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedAxesSkill)}
Daggers - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatDaggersSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatDaggersSkill)}
{player.spec === Spec.SpecFeralDruid && (
Feral Combat - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatFeralCombatSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatFeralCombatSkill)}
)}
Maces - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatMacesSkill)} /{' '} - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedMacesSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatMacesSkill)} /{' '} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedMacesSkill)}
Polearms - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatPolearmsSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatPolearmsSkill)}
Staves - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatStavesSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatStavesSkill)}
Swords - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatSwordsSkill)} /{' '} - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatSwordsSkill)} /{' '} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)}
Unarmed - {this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatUnarmedSkill)} + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatUnarmedSkill)}
, ); @@ -327,15 +318,27 @@ export class CharacterStats extends Component { }); if (this.meleeCritCapValueElem) { - const meleeCritCapInfo = player.getMeleeCritCapInfo(); + const has2hWeapon = player.getGear().getEquippedItem(ItemSlot.ItemSlotMainHand)?.item.handType == HandType.HandTypeTwoHand; + const mhWeapon: WeaponType = player.getGear().getEquippedItem(ItemSlot.ItemSlotMainHand)?.item.weaponType as WeaponType; + const ohWeapon: WeaponType = player.getGear().getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.weaponType as WeaponType; + const mhCritCapInfo = player.getMeleeCritCapInfo(mhWeapon, has2hWeapon); + const ohCritCapInfo = player.getMeleeCritCapInfo(ohWeapon, has2hWeapon); + + const playerCritCapDelta = mhCritCapInfo.playerCritCapDelta; + + if (playerCritCapDelta === 0.0) { + const prefix = 'Exact'; + } + + const prefix = playerCritCapDelta > 0 ? 'Over by ' : 'Under by '; const valueElem = ( - {`${this.meleeCritCapDisplayString(player, finalStats)} `} + {`${prefix} ${Math.abs(playerCritCapDelta).toFixed(2)}%`} ); - const capDelta = meleeCritCapInfo.playerCritCapDelta; + const capDelta = mhCritCapInfo.playerCritCapDelta; if (capDelta === 0) { valueElem.classList.add('text-white'); } else if (capDelta > 0) { @@ -349,40 +352,90 @@ export class CharacterStats extends Component { const tooltipContent = (
-
- Glancing: - {`${meleeCritCapInfo.glancing.toFixed(2)}%`} -
-
- Suppression: - {`${meleeCritCapInfo.suppression.toFixed(2)}%`} -
-
- To Hit Cap: - {`${meleeCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} -
-
- To Exp Cap: - {`${meleeCritCapInfo.remainingExpertiseCap.toFixed(2)}%`} -
-
- Debuffs: - {`${meleeCritCapInfo.debuffCrit.toFixed(2)}%`} -
- {meleeCritCapInfo.specSpecificOffset != 0 && ( +
+
+ Main Hand + +
+
+
+ Glancing: + {`${mhCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Suppression: + {`${mhCritCapInfo.suppression.toFixed(2)}%`} +
+
+ White Miss: + {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+
+ Dodge: + {`${mhCritCapInfo.dodgeCap.toFixed(2)}%`} +
+
+ Parry: + {`${mhCritCapInfo.parryCap.toFixed(2)}%`} +
+ {mhCritCapInfo.specSpecificOffset != 0 && ( +
+ Spec Offsets: + {`${mhCritCapInfo.specSpecificOffset.toFixed(2)}%`} +
+ )} +
+ Final Crit Cap: + {`${mhCritCapInfo.baseCritCap.toFixed(2)}%`} +
+
- Spec Offsets: - {`${meleeCritCapInfo.specSpecificOffset.toFixed(2)}%`} + Can Raise By: + {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}
- )} -
- Final Crit Cap: - {`${meleeCritCapInfo.baseCritCap.toFixed(2)}%`} +
-
-
- Can Raise By: - {`${(meleeCritCapInfo.remainingExpertiseCap + meleeCritCapInfo.remainingMeleeHitCap).toFixed(2)}%`} +
+
+ Off Hand + +
+
+
+ Glancing: + {`${ohCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Suppression: + {`${ohCritCapInfo.suppression.toFixed(2)}%`} +
+
+ White Miss: + {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+
+ Dodge: + {`${ohCritCapInfo.dodgeCap.toFixed(2)}%`} +
+
+ Parry: + {`${ohCritCapInfo.parryCap.toFixed(2)}%`} +
+ {ohCritCapInfo.specSpecificOffset != 0 && ( +
+ Spec Offsets: + {`${ohCritCapInfo.specSpecificOffset.toFixed(2)}%`} +
+ )} +
+ Final Crit Cap: + {`${ohCritCapInfo.baseCritCap.toFixed(2)}%`} +
+
+
+ Can Raise By: + {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
); @@ -403,7 +456,7 @@ export class CharacterStats extends Component { if (stat === Stat.StatBlockValue) { const mult = stats.getPseudoStat(PseudoStat.PseudoStatBlockValueMultiplier) || 1; const perStr = Math.max(0, stats.getPseudoStat(PseudoStat.PseudoStatBlockValuePerStrength) * deltaStats.getStat(Stat.StatStrength) - 1); - displayStr = String(Math.round((rawValue * mult) + perStr)); + displayStr = String(Math.round(rawValue * mult + perStr)); } else if (stat === Stat.StatMeleeHit) { displayStr = `${(rawValue / Mechanics.MELEE_HIT_RATING_PER_HIT_CHANCE).toFixed(2)}%`; } else if (stat === Stat.StatSpellHit) { @@ -447,7 +500,7 @@ export class CharacterStats extends Component { displayStr = `${rawValue} (${(rawValue / Mechanics.RESILIENCE_RATING_PER_CRIT_REDUCTION_CHANCE).toFixed(2)}%)`; } } - + if (!displayStr) displayStr = String(Math.round(rawValue)); return displayStr; @@ -516,12 +569,10 @@ export class CharacterStats extends Component { } private shouldShowMeleeCritCap(player: Player): boolean { - // TODO: Disabled for now while we fix displayed crit cap - return false; - // return [Spec.SpecEnhancementShaman, Spec.SpecRetributionPaladin, Spec.SpecRogue, Spec.SpecWarrior, Spec.SpecHunter].includes(player.spec); + return [Spec.SpecEnhancementShaman, Spec.SpecRetributionPaladin, Spec.SpecRogue, Spec.SpecWarrior, Spec.SpecHunter].includes(player.spec); } - private meleeCritCapDisplayString(player: Player, _: Stats): string { + /*private meleeCritCapDisplayString(player: Player, _: Stats): string { const playerCritCapDelta = player.getMeleeCritCap(); if (playerCritCapDelta === 0.0) { @@ -530,5 +581,5 @@ export class CharacterStats extends Component { const prefix = playerCritCapDelta > 0 ? 'Over by ' : 'Under by '; return `${prefix} ${Math.abs(playerCritCapDelta).toFixed(2)}%`; - } + }*/ } diff --git a/ui/core/player.ts b/ui/core/player.ts index 7ebe0aba4..5cc398fea 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -30,6 +30,7 @@ import { Stat, UnitReference, UnitStats, + WeaponType, } from './proto/common.js'; import { DungeonFilterOption, @@ -181,9 +182,9 @@ export interface MeleeCritCapInfo { debuffCrit: number; hasOffhandWeapon: boolean; meleeHitCap: number; - expertiseCap: number; remainingMeleeHitCap: number; - remainingExpertiseCap: number; + dodgeCap: number; + parryCap: number; baseCritCap: number; specSpecificOffset: number; playerCritCapDelta: number; @@ -688,20 +689,67 @@ export class Player { this.bonusStatsChangeEmitter.emit(eventID); } - getMeleeCritCapInfo(): MeleeCritCapInfo { + getMeleeCritCapInfo(weapon: WeaponType, has2hWeapon: boolean): MeleeCritCapInfo { + let defenderDefense = 315.0; // Initializes at level 63 until UI is loaded + if (this.sim.encounter.targets) { + const targetlevel = this.sim.encounter?.primaryTarget.level; + defenderDefense = targetlevel * 5; + } + const suppression = 4.8; + const glancing = 40.0; + + let weaponSkill = 300.0; const meleeCrit = (this.currentStats.finalStats?.stats[Stat.StatMeleeCrit] || 0.0) / Mechanics.MELEE_CRIT_RATING_PER_CRIT_CHANCE; const meleeHit = (this.currentStats.finalStats?.stats[Stat.StatMeleeHit] || 0.0) / Mechanics.MELEE_HIT_RATING_PER_HIT_CHANCE; const expertise = (this.currentStats.finalStats?.stats[Stat.StatExpertise] || 0.0) / Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION / 4; - const suppression = 4.8; - const glancing = 40.0; + const hasOffhandWeapon = this.getGear().getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.weaponType !== undefined; + + if (!has2hWeapon) { + switch (weapon) { + case WeaponType.WeaponTypeUnknown: + break; + case WeaponType.WeaponTypeAxe: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatAxesSkill] || 0.0; + break; + case WeaponType.WeaponTypeDagger: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatDaggersSkill] || 0.0; + break; + case WeaponType.WeaponTypeFist: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatUnarmedSkill] || 0.0; + break; + case WeaponType.WeaponTypeMace: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatMacesSkill] || 0.0; + break; + case WeaponType.WeaponTypeSword: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatSwordsSkill] || 0.0; + break; + } + } + if (has2hWeapon) { + switch (weapon) { + case WeaponType.WeaponTypeUnknown: + break; + case WeaponType.WeaponTypeAxe: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedAxesSkill] || 0.0; + break; + case WeaponType.WeaponTypeMace: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedMacesSkill] || 0.0; + break; + case WeaponType.WeaponTypeSword: + weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedSwordsSkill] || 0.0; + break; + } + } - const hasOffhandWeapon = this.getGear().getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.weaponSpeed !== undefined; - // Due to warrior HS bug, hit cap for crit cap calculation should be 8% instead of 27% - const meleeHitCap = hasOffhandWeapon && this.spec !== Spec.SpecWarrior ? 27.0 : 8.0; - const dodgeCap = 6.5; - const parryCap = this.getInFrontOfTarget() ? 14.0 : 0; - const expertiseCap = dodgeCap + parryCap; + // Due to warrior HS bug, hit cap for crit cap calculation ignores the 19% penalty + let meleeHitCap = + defenderDefense - weaponSkill <= 10 + ? 5.0 + (defenderDefense - weaponSkill) * 0.1 + : 5.0 + (defenderDefense - weaponSkill) * 0.2 + (defenderDefense - weaponSkill - 10) * 0.2; + meleeHitCap = hasOffhandWeapon && this.spec !== Spec.SpecWarrior ? meleeHitCap + 19.0 : meleeHitCap + 0.0; + const dodgeCap = 5.0 + (defenderDefense - weaponSkill) * 0.1; + const parryCap = this.getInFrontOfTarget() ? 14.0 : 0; const remainingMeleeHitCap = Math.max(meleeHitCap - meleeHit, 0.0); const remainingDodgeCap = Math.max(dodgeCap - expertise, 0.0); const remainingParryCap = Math.max(parryCap - expertise, 0.0); @@ -731,19 +779,15 @@ export class Player { debuffCrit, hasOffhandWeapon, meleeHitCap, - expertiseCap, - remainingMeleeHitCap, - remainingExpertiseCap, + dodgeCap, + parryCap, baseCritCap, specSpecificOffset, playerCritCapDelta, + remainingMeleeHitCap, }; } - getMeleeCritCap() { - return this.getMeleeCritCapInfo().playerCritCapDelta; - } - getSimpleRotation(): SpecRotation { const jsonStr = this.aplRotation.simple?.specRotationJson || ''; if (!jsonStr) { @@ -1112,7 +1156,7 @@ export class Player { filterItemData(itemData: Array, getItemFunc: (val: T) => Item, slot: ItemSlot): Array { const filters = this.sim.getFilters(); - + const filterItems = (itemData: Array, filterFunc: (item: Item) => boolean) => { return itemData.filter(itemElem => filterFunc(getItemFunc(itemElem))); }; @@ -1150,7 +1194,7 @@ export class Player { const zoneId = DungeonFilterOption[zoneName]; if (typeof zoneId === 'number' && zoneId !== 0 && !filters.raids.includes(zoneId)) { - zoneIds.push(zoneId) + zoneIds.push(zoneId); } } @@ -1167,7 +1211,7 @@ export class Player { const zoneId = RaidFilterOption[zoneName]; if (typeof zoneId === 'number' && zoneId !== 0 && !filters.raids.includes(zoneId)) { - zoneIds.push(zoneId) + zoneIds.push(zoneId); } } From 40bfedab20ebe85eb2abecb4fa2530c10d57686f Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Fri, 10 Jan 2025 10:39:08 -0700 Subject: [PATCH 2/3] updates --- ui/core/components/character_stats.tsx | 184 +++++++++++++------------ 1 file changed, 94 insertions(+), 90 deletions(-) diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 4911e098a..abd69dccf 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -238,27 +238,31 @@ export class CharacterStats extends Component {
Axes - - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatAxesSkill)} /{' '} - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedAxesSkill)} - + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatAxesSkill)} +
+
+ 2H Axes + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedAxesSkill)}
Daggers {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatDaggersSkill)}
+ {/* Commenting out feral combat skill since not present in Classic. {player.spec === Spec.SpecFeralDruid && (
Feral Combat {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatFeralCombatSkill)}
)} + */}
Maces - - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatMacesSkill)} /{' '} - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedMacesSkill)} - + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatMacesSkill)} +
+
+ 2H Maces + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedMacesSkill)}
Polearms @@ -270,10 +274,11 @@ export class CharacterStats extends Component {
Swords - - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatSwordsSkill)} /{' '} - {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)} - + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatSwordsSkill)} +
+
+ 2H Swords + {this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)}
Unarmed @@ -352,91 +357,90 @@ export class CharacterStats extends Component { const tooltipContent = (
-
-
- Main Hand - -
-
-
- Glancing: - {`${mhCritCapInfo.glancing.toFixed(2)}%`} -
-
- Suppression: - {`${mhCritCapInfo.suppression.toFixed(2)}%`} -
-
- White Miss: - {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} -
-
- Dodge: - {`${mhCritCapInfo.dodgeCap.toFixed(2)}%`} -
+
+ Main Hand + +
+
+
+ Glancing: + {`${mhCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Suppression: + {`${mhCritCapInfo.suppression.toFixed(2)}%`} +
+
+ White Miss: + {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+
+ Dodge: + {`${mhCritCapInfo.dodgeCap.toFixed(2)}%`} +
+
+ Parry: + {`${mhCritCapInfo.parryCap.toFixed(2)}%`} +
+ {mhCritCapInfo.specSpecificOffset != 0 && (
- Parry: - {`${mhCritCapInfo.parryCap.toFixed(2)}%`} + Spec Offsets: + {`${mhCritCapInfo.specSpecificOffset.toFixed(2)}%`}
- {mhCritCapInfo.specSpecificOffset != 0 && ( + )} +
+ Final Crit Cap: + {`${mhCritCapInfo.baseCritCap.toFixed(2)}%`} +
+
+ Can Raise By: + {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+ {!has2hWeapon && ( +
+
+
- Spec Offsets: - {`${mhCritCapInfo.specSpecificOffset.toFixed(2)}%`} + Off Hand +
- )} -
- Final Crit Cap: - {`${mhCritCapInfo.baseCritCap.toFixed(2)}%`} -
-
-
- Can Raise By: - {`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} -
-
-
-
-
- Off Hand - -
-
-
- Glancing: - {`${ohCritCapInfo.glancing.toFixed(2)}%`} -
-
- Suppression: - {`${ohCritCapInfo.suppression.toFixed(2)}%`} -
-
- White Miss: - {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} -
-
- Dodge: - {`${ohCritCapInfo.dodgeCap.toFixed(2)}%`} -
-
- Parry: - {`${ohCritCapInfo.parryCap.toFixed(2)}%`} -
- {ohCritCapInfo.specSpecificOffset != 0 && ( +
- Spec Offsets: - {`${ohCritCapInfo.specSpecificOffset.toFixed(2)}%`} + Glancing: + {`${ohCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Suppression: + {`${ohCritCapInfo.suppression.toFixed(2)}%`} +
+
+ White Miss: + {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+
+ Dodge: + {`${ohCritCapInfo.dodgeCap.toFixed(2)}%`} +
+
+ Parry: + {`${ohCritCapInfo.parryCap.toFixed(2)}%`} +
+ {ohCritCapInfo.specSpecificOffset != 0 && ( +
+ Spec Offsets: + {`${ohCritCapInfo.specSpecificOffset.toFixed(2)}%`} +
+ )} +
+ Final Crit Cap: + {`${ohCritCapInfo.baseCritCap.toFixed(2)}%`} +
+
+ Can Raise By: + {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}
- )} -
- Final Crit Cap: - {`${ohCritCapInfo.baseCritCap.toFixed(2)}%`} -
-
-
- Can Raise By: - {`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}
-
+ )}
); From ff7897b59d78642ffa25d1e2f05b3f7d52639e91 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Fri, 10 Jan 2025 18:59:31 -0700 Subject: [PATCH 3/3] adjusts to level scaling --- ui/core/components/character_stats.tsx | 11 ------ ui/core/components/encounter_picker.ts | 12 ------- ui/core/player.ts | 46 ++++++++++++++------------ 3 files changed, 25 insertions(+), 44 deletions(-) diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index abd69dccf..fa11dcbd1 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -575,15 +575,4 @@ export class CharacterStats extends Component { private shouldShowMeleeCritCap(player: Player): boolean { return [Spec.SpecEnhancementShaman, Spec.SpecRetributionPaladin, Spec.SpecRogue, Spec.SpecWarrior, Spec.SpecHunter].includes(player.spec); } - - /*private meleeCritCapDisplayString(player: Player, _: Stats): string { - const playerCritCapDelta = player.getMeleeCritCap(); - - if (playerCritCapDelta === 0.0) { - return 'Exact'; - } - - const prefix = playerCritCapDelta > 0 ? 'Over by ' : 'Under by '; - return `${prefix} ${Math.abs(playerCritCapDelta).toFixed(2)}%`; - }*/ } diff --git a/ui/core/components/encounter_picker.ts b/ui/core/components/encounter_picker.ts index feabb5074..829de2723 100644 --- a/ui/core/components/encounter_picker.ts +++ b/ui/core/components/encounter_picker.ts @@ -320,18 +320,6 @@ class TargetPicker extends Input { { name: '62', value: 62 }, { name: '61', value: 61 }, { name: '60', value: 60 }, - { name: '53', value: 53 }, - { name: '52', value: 52 }, - { name: '51', value: 51 }, - { name: '50', value: 50 }, - { name: '43', value: 43 }, - { name: '42', value: 42 }, - { name: '41', value: 41 }, - { name: '40', value: 40 }, - { name: '28', value: 28 }, - { name: '27', value: 27 }, - { name: '26', value: 26 }, - { name: '25', value: 25 }, ], changedEvent: () => encounter.targetsChangeEmitter, getValue: () => this.getTarget().level, diff --git a/ui/core/player.ts b/ui/core/player.ts index 5cc398fea..0c31b902d 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -177,8 +177,8 @@ export interface MeleeCritCapInfo { meleeCrit: number; meleeHit: number; expertise: number; - suppression: number; glancing: number; + suppression: number; debuffCrit: number; hasOffhandWeapon: boolean; meleeHitCap: number; @@ -690,13 +690,14 @@ export class Player { } getMeleeCritCapInfo(weapon: WeaponType, has2hWeapon: boolean): MeleeCritCapInfo { - let defenderDefense = 315.0; // Initializes at level 63 until UI is loaded + let targetLevel = 63; // Initializes at level 63 until UI is loaded if (this.sim.encounter.targets) { - const targetlevel = this.sim.encounter?.primaryTarget.level; - defenderDefense = targetlevel * 5; + targetLevel = this.sim.encounter?.primaryTarget.level; } - const suppression = 4.8; - const glancing = 40.0; + const levelDiff = targetLevel - Mechanics.MAX_CHARACTER_LEVEL; + const defenderDefense = targetLevel * 5; + const glancing = (1 + levelDiff) * 10.0; + const suppression = levelDiff === 3 ? levelDiff + 1.8 : levelDiff; let weaponSkill = 300.0; const meleeCrit = (this.currentStats.finalStats?.stats[Stat.StatMeleeCrit] || 0.0) / Mechanics.MELEE_CRIT_RATING_PER_CRIT_CHANCE; @@ -704,24 +705,26 @@ export class Player { const expertise = (this.currentStats.finalStats?.stats[Stat.StatExpertise] || 0.0) / Mechanics.EXPERTISE_PER_QUARTER_PERCENT_REDUCTION / 4; const hasOffhandWeapon = this.getGear().getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.weaponType !== undefined; + const getWeaponSkillForWeaponType = (skill: PseudoStat) => this.currentStats.talentsStats?.pseudoStats[skill] || 0.0; + if (!has2hWeapon) { switch (weapon) { case WeaponType.WeaponTypeUnknown: break; case WeaponType.WeaponTypeAxe: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatAxesSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatAxesSkill); break; case WeaponType.WeaponTypeDagger: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatDaggersSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatDaggersSkill); break; case WeaponType.WeaponTypeFist: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatUnarmedSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatUnarmedSkill); break; case WeaponType.WeaponTypeMace: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatMacesSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatMacesSkill); break; case WeaponType.WeaponTypeSword: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatSwordsSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatSwordsSkill); break; } } @@ -730,26 +733,27 @@ export class Player { case WeaponType.WeaponTypeUnknown: break; case WeaponType.WeaponTypeAxe: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedAxesSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatTwoHandedAxesSkill); break; case WeaponType.WeaponTypeMace: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedMacesSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatTwoHandedMacesSkill); break; case WeaponType.WeaponTypeSword: - weaponSkill += this.currentStats.talentsStats?.pseudoStats[PseudoStat.PseudoStatTwoHandedSwordsSkill] || 0.0; + weaponSkill += getWeaponSkillForWeaponType(PseudoStat.PseudoStatTwoHandedSwordsSkill); break; } } + const skillDiff = defenderDefense - weaponSkill; // Due to warrior HS bug, hit cap for crit cap calculation ignores the 19% penalty - let meleeHitCap = - defenderDefense - weaponSkill <= 10 - ? 5.0 + (defenderDefense - weaponSkill) * 0.1 - : 5.0 + (defenderDefense - weaponSkill) * 0.2 + (defenderDefense - weaponSkill - 10) * 0.2; + let meleeHitCap = skillDiff <= 10 ? 5.0 + skillDiff * 0.1 : 5.0 + skillDiff * 0.2 + (skillDiff - 10) * 0.2; meleeHitCap = hasOffhandWeapon && this.spec !== Spec.SpecWarrior ? meleeHitCap + 19.0 : meleeHitCap + 0.0; - const dodgeCap = 5.0 + (defenderDefense - weaponSkill) * 0.1; - const parryCap = this.getInFrontOfTarget() ? 14.0 : 0; + const dodgeCap = 5.0 + skillDiff * 0.1; + let parryCap = 0.0; + if (this.getInFrontOfTarget()) { + parryCap = levelDiff === 3 ? 14.0 : 5.0 + skillDiff * 0.1; // 14% parry at +3 level and follows dodge scaling otherwise + } const remainingMeleeHitCap = Math.max(meleeHitCap - meleeHit, 0.0); const remainingDodgeCap = Math.max(dodgeCap - expertise, 0.0); const remainingParryCap = Math.max(parryCap - expertise, 0.0); @@ -774,8 +778,8 @@ export class Player { meleeCrit, meleeHit, expertise, - suppression, glancing, + suppression, debuffCrit, hasOffhandWeapon, meleeHitCap,