Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at handedness crit cap for Classic #75

Merged
merged 3 commits into from
Jan 11, 2025
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
178 changes: 111 additions & 67 deletions ui/core/components/character_stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -12,13 +12,7 @@ import { NumberPicker } from './number_picker';
export type StatMods = { talents?: Stats; buffs?: Stats };

const statGroups = new Map<string, Array<UnitStat>>([
[
'Primary',
[
UnitStat.fromStat(Stat.StatHealth),
UnitStat.fromStat(Stat.StatMana),
],
],
['Primary', [UnitStat.fromStat(Stat.StatHealth), UnitStat.fromStat(Stat.StatMana)]],
[
'Attributes',
[
Expand All @@ -27,7 +21,7 @@ const statGroups = new Map<string, Array<UnitStat>>([
UnitStat.fromStat(Stat.StatStamina),
UnitStat.fromStat(Stat.StatIntellect),
UnitStat.fromStat(Stat.StatSpirit),
]
],
],
[
'Physical',
Expand All @@ -40,7 +34,7 @@ const statGroups = new Map<string, Array<UnitStat>>([
UnitStat.fromStat(Stat.StatMeleeCrit),
UnitStat.fromStat(Stat.StatMeleeHaste),
UnitStat.fromPseudoStat(PseudoStat.BonusPhysicalDamage),
]
],
],
[
'Spell',
Expand All @@ -58,7 +52,7 @@ const statGroups = new Map<string, Array<UnitStat>>([
UnitStat.fromStat(Stat.StatSpellHaste),
UnitStat.fromStat(Stat.StatSpellPenetration),
UnitStat.fromStat(Stat.StatMP5),
]
],
],
[
'Defense',
Expand All @@ -70,7 +64,7 @@ const statGroups = new Map<string, Array<UnitStat>>([
UnitStat.fromStat(Stat.StatParry),
UnitStat.fromStat(Stat.StatBlock),
UnitStat.fromStat(Stat.StatBlockValue),
]
],
],
[
'Resistance',
Expand All @@ -80,13 +74,10 @@ const statGroups = new Map<string, Array<UnitStat>>([
UnitStat.fromStat(Stat.StatFrostResistance),
UnitStat.fromStat(Stat.StatNatureResistance),
UnitStat.fromStat(Stat.StatShadowResistance),
]
],
[
'Misc',
[]
],
],
])
['Misc', []],
]);

export class CharacterStats extends Component {
readonly stats: Array<UnitStat>;
Expand Down Expand Up @@ -247,46 +238,51 @@ export class CharacterStats extends Component {
<div className="ps-2">
<div className="character-stats-tooltip-row">
<span>Axes</span>
<span>
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatAxesSkill)} /{' '}
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedAxesSkill)}
</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatAxesSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>2H Axes</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedAxesSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Daggers</span>
<span>{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatDaggersSkill)}</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatDaggersSkill)}</span>
</div>
{/* Commenting out feral combat skill since not present in Classic.
{player.spec === Spec.SpecFeralDruid && (
<div className="character-stats-tooltip-row">
<span>Feral Combat</span>
<span>{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatFeralCombatSkill)}</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatFeralCombatSkill)}</span>
</div>
)}
*/}
<div className="character-stats-tooltip-row">
<span>Maces</span>
<span>
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatMacesSkill)} /{' '}
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedMacesSkill)}
</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatMacesSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>2H Maces</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedMacesSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Polearms</span>
<span>{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatPolearmsSkill)}</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatPolearmsSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Staves</span>
<span>{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatStavesSkill)}</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatStavesSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Swords</span>
<span>
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatSwordsSkill)} /{' '}
{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)}
</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatSwordsSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>2H Swords</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatTwoHandedSwordsSkill)}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Unarmed</span>
<span>{this.weaponSkillDisplayString(gearStats, PseudoStat.PseudoStatUnarmedSkill)}</span>
<span>{this.weaponSkillDisplayString(talentsStats, PseudoStat.PseudoStatUnarmedSkill)}</span>
</div>
</div>,
);
Expand Down Expand Up @@ -327,15 +323,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 = (
<a href="javascript:void(0)" className="stat-value-link" attributes={{ role: 'button' }}>
{`${this.meleeCritCapDisplayString(player, finalStats)} `}
{`${prefix} ${Math.abs(playerCritCapDelta).toFixed(2)}%`}
</a>
);

const capDelta = meleeCritCapInfo.playerCritCapDelta;
const capDelta = mhCritCapInfo.playerCritCapDelta;
if (capDelta === 0) {
valueElem.classList.add('text-white');
} else if (capDelta > 0) {
Expand All @@ -349,41 +357,90 @@ export class CharacterStats extends Component {

const tooltipContent = (
<div>
<div className="character-stats-tooltip-row">
<span>Main Hand</span>
<span></span>
</div>
<hr />
<div className="character-stats-tooltip-row">
<span>Glancing:</span>
<span>{`${meleeCritCapInfo.glancing.toFixed(2)}%`}</span>
<span>{`${mhCritCapInfo.glancing.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Suppression:</span>
<span>{`${meleeCritCapInfo.suppression.toFixed(2)}%`}</span>
<span>{`${mhCritCapInfo.suppression.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>To Hit Cap:</span>
<span>{`${meleeCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}</span>
<span>White Miss:</span>
<span>{`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>To Exp Cap:</span>
<span>{`${meleeCritCapInfo.remainingExpertiseCap.toFixed(2)}%`}</span>
<span>Dodge:</span>
<span>{`${mhCritCapInfo.dodgeCap.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Debuffs:</span>
<span>{`${meleeCritCapInfo.debuffCrit.toFixed(2)}%`}</span>
<span>Parry:</span>
<span>{`${mhCritCapInfo.parryCap.toFixed(2)}%`}</span>
</div>
{meleeCritCapInfo.specSpecificOffset != 0 && (
{mhCritCapInfo.specSpecificOffset != 0 && (
<div className="character-stats-tooltip-row">
<span>Spec Offsets:</span>
<span>{`${meleeCritCapInfo.specSpecificOffset.toFixed(2)}%`}</span>
<span>{`${mhCritCapInfo.specSpecificOffset.toFixed(2)}%`}</span>
</div>
)}
<div className="character-stats-tooltip-row">
<span>Final Crit Cap:</span>
<span>{`${meleeCritCapInfo.baseCritCap.toFixed(2)}%`}</span>
<span>{`${mhCritCapInfo.baseCritCap.toFixed(2)}%`}</span>
</div>
<hr />
<div className="character-stats-tooltip-row">
<span>Can Raise By:</span>
<span>{`${(meleeCritCapInfo.remainingExpertiseCap + meleeCritCapInfo.remainingMeleeHitCap).toFixed(2)}%`}</span>
<span>{`${mhCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}</span>
</div>
{!has2hWeapon && (
<div>
<hr />

<div className="character-stats-tooltip-row">
<span>Off Hand</span>
<span></span>
</div>
<hr />
<div className="character-stats-tooltip-row">
<span>Glancing:</span>
<span>{`${ohCritCapInfo.glancing.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Suppression:</span>
<span>{`${ohCritCapInfo.suppression.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>White Miss:</span>
<span>{`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Dodge:</span>
<span>{`${ohCritCapInfo.dodgeCap.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Parry:</span>
<span>{`${ohCritCapInfo.parryCap.toFixed(2)}%`}</span>
</div>
{ohCritCapInfo.specSpecificOffset != 0 && (
<div className="character-stats-tooltip-row">
<span>Spec Offsets:</span>
<span>{`${ohCritCapInfo.specSpecificOffset.toFixed(2)}%`}</span>
</div>
)}
<div className="character-stats-tooltip-row">
<span>Final Crit Cap:</span>
<span>{`${ohCritCapInfo.baseCritCap.toFixed(2)}%`}</span>
</div>
<div className="character-stats-tooltip-row">
<span>Can Raise By:</span>
<span>{`${ohCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`}</span>
</div>
</div>
)}
</div>
);

Expand All @@ -403,7 +460,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) {
Expand Down Expand Up @@ -447,7 +504,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;
Expand Down Expand Up @@ -516,19 +573,6 @@ export class CharacterStats extends Component {
}

private shouldShowMeleeCritCap(player: Player<any>): 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);
}

private meleeCritCapDisplayString(player: Player<any>, _: 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)}%`;
return [Spec.SpecEnhancementShaman, Spec.SpecRetributionPaladin, Spec.SpecRogue, Spec.SpecWarrior, Spec.SpecHunter].includes(player.spec);
}
}
12 changes: 0 additions & 12 deletions ui/core/components/encounter_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,18 +320,6 @@ class TargetPicker extends Input<Encounter, TargetProto> {
{ 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,
Expand Down
Loading
Loading