Conversation
|
https://www.wowhead.com/news/new-ghostcrawler-blog-mists-of-pandaria-stat-changes-201233 I missed one thing - updating Block to be a second roll, after Miss/Dodge/Parry (assuming this applies to Mobs as well and not just players) I need to sleep now so if someone else gets to that, go for it. |
|
Need to update Enemy->Player block rolls Crit suppression source: https://www.bluetracker.gg/wow/topic/us-en/5889309137-beta-class-balance-analysis/#97 |
| func (spell *Spell) OutcomeExpectedMeleeWhite(_ *Simulation, result *SpellResult, attackTable *AttackTable) { | ||
| missChance := spell.GetPhysicalMissChance(attackTable) | ||
| dodgeChance := TernaryFloat64(spell.Flags.Matches(SpellFlagCannotBeDodged), 0, max(0, attackTable.BaseDodgeChance-spell.DodgeParrySuppression()-spell.Unit.PseudoStats.DodgeReduction)) | ||
| parryChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, max(0, attackTable.BaseParryChance-spell.DodgeParrySuppression()), 0) | ||
| dodgeChance := TernaryFloat64(spell.Flags.Matches(SpellFlagCannotBeDodged), 0, max(0, attackTable.BaseDodgeChance-spell.DodgeSuppression()-spell.Unit.PseudoStats.DodgeReduction)) | ||
| parryChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, max(0, attackTable.BaseParryChance-spell.ParrySuppression()), 0) | ||
| glanceChance := attackTable.BaseGlanceChance | ||
| blockChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, attackTable.BaseBlockChance, 0) | ||
| whiteCritCap := 1.0 - missChance - dodgeChance - parryChance - glanceChance - blockChance |
There was a problem hiding this comment.
The formulas for whiteCritCap and averageMultiplier also need to be updated here.
There was a problem hiding this comment.
Crit cap makes sense, but I'm not sure there's anything needed for averageMultiplier...?
| func (spell *Spell) OutcomeExpectedMeleeWeaponSpecialHitAndCrit(_ *Simulation, result *SpellResult, attackTable *AttackTable) { | ||
| missChance := max(0, attackTable.BaseMissChance-spell.PhysicalHitChance(attackTable)) | ||
| dodgeChance := TernaryFloat64(spell.Flags.Matches(SpellFlagCannotBeDodged), 0, max(0, attackTable.BaseDodgeChance-spell.DodgeParrySuppression()-spell.Unit.PseudoStats.DodgeReduction)) | ||
| parryChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, max(0, attackTable.BaseParryChance-spell.DodgeParrySuppression()), 0) | ||
| dodgeChance := TernaryFloat64(spell.Flags.Matches(SpellFlagCannotBeDodged), 0, max(0, attackTable.BaseDodgeChance-spell.DodgeSuppression()-spell.Unit.PseudoStats.DodgeReduction)) | ||
| parryChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, max(0, attackTable.BaseParryChance-spell.ParrySuppression()), 0) | ||
| blockChance := TernaryFloat64(spell.Unit.PseudoStats.InFrontOfTarget, attackTable.BaseBlockChance, 0) | ||
| critChance := spell.PhysicalCritChance(attackTable) | ||
| averageMultiplier := (1.0 - missChance - dodgeChance - parryChance) * (1.0 + (spell.CritDamageMultiplier()-1)*critChance) |
There was a problem hiding this comment.
I think these formulas should still hold though in MoP, right? Worth a double check.
| parrySupp := expertiseRating / ExpertisePerQuarterPercentReduction / 400 | ||
| parrySupp -= max(0, spell.DodgeSuppression()) |
There was a problem hiding this comment.
There's a bug here: if the player has 10% Expertise then we should calculate 2.5% Parry suppression against a raid boss, but this formula will return 0 since spell.DodgeSuppression() will return 10%. I think this method will also need to take in an attackTable input to work properly in MoP, since you need access to attackTable.BaseDodgeChance and spell.Unit.PseudoStats.DodgeReduction in order to determine the correct amount of Expertise that was already "spent" on Dodge suppression.
There was a problem hiding this comment.
I think we'll need exploration of what would use the DodgeReduction field, and if that actually impacts the Parry outcome too. I don't actually know/see anything interacting with that field anymore.
| if defender.Type == EnemyUnit { | ||
| // Assumes attacker (the Player) is level 80. |
There was a problem hiding this comment.
Delete this comment, since I don't think there is any assumption required on player level for these calculations, just on the relative difference between the attacker and target levels.
| } else { | ||
| // Assumes defender (the Player) is level 80. |
| } else { | ||
| // Assumes defender (the Player) is level 80. | ||
| table.BaseSpellMissChance = 0.05 |
There was a problem hiding this comment.
Have you checked the NPC-attacking-player values below as well for correctness in MoP?
|
https://www.engadget.com/2012-03-08-scattered-shots-hunter-expertise-in-mists-of-pandaria.html Those blogs and guides indicate that Ranged attacks should now be dodgeable. So we might want to add that as well here? |
Yep the default ranged outcome should include dodges and perhaps add one for NoDodge? If a spell matching that even exists |
|
Just leaving more notes as I research things in between real work: |
|
I've left the negative Dodge/Parry/Block Base values for Enemy->Player as I assume those are needed for accurate mitigation calculations, even though an actual negative value should never happen. Miss/Spell Miss I've left at 0 for 92/93 since there's no way of mitigating that anymore I forgot ranged lmao |
| if dot.Spell.Unit.PseudoStats.InFrontOfTarget { | ||
| if !result.applyAttackTableMissNoDWPenalty(dot.Spell, attackTable, roll, &chance) { |
There was a problem hiding this comment.
Do you need to handle dodges here as well?
There was a problem hiding this comment.
Snapshot initially made me think it was used in DoTs like Explosive Shot (Which can be tested to see how Dodges work once it's fixed), but looking closer I don't see anywhere it gets used
| // TODO: Ranged Attacks are currently blockable on MoP Beta | ||
| // However, they shouldn't be! Revisit when fixed |
There was a problem hiding this comment.
Ranged attacks are also not currently dodge-able on Beta. Both are known issues that will likely be fixed, so we should handle them the same way: either make both Dodge and Block function like 5.4 in the sim under the assumption that both will be fixed, or make both function like Beta with a note to revisit both.
| *chance += max(0, attackTable.BaseDodgeChance-spell.DodgeParrySuppression()-spell.Unit.PseudoStats.DodgeReduction) | ||
| *chance += max(0, attackTable.BaseDodgeChance-spell.DodgeSuppression()-spell.Unit.PseudoStats.DodgeReduction) |
There was a problem hiding this comment.
PseudoStats.DodgeReduction has been unused since WotLK, so I think you can delete it outright from the MoP codebase. Likewise, you should be able to delete the suppress_dodge field from proto.Target.
| critChance := min(spell.PhysicalCritChance(attackTable), whiteCritCap) | ||
| averageMultiplier := 1.0 - missChance - dodgeChance - parryChance + (spell.CritDamageMultiplier()-1)*critChance - glanceChance*(1.0-attackTable.GlanceMultiplier) - blockChance*result.Target.BlockDamageReduction() |
There was a problem hiding this comment.
I mathed it out, the correct formula for the MoP 2-roll white table should be:
averageMultiplier := (1.0 - missChance - dodgeChance - parryChance + (spell.CritDamageMultiplier()-1)*critChance - glanceChance*(1.0-attackTable.GlanceMultiplier)) * (1.0 - blockChance*result.Target.BlockDamageReduction())
Essentially, the average Block damage reduction from the second roll now gets multiplied across all the terms due to the possibility of Crit-Block and Glance-Block outcomes in MoP.
| critChance := spell.PhysicalCritChance(attackTable) | ||
| averageMultiplier := (1.0 - missChance - dodgeChance - parryChance) * (1.0 + (spell.CritDamageMultiplier()-1)*critChance) |
There was a problem hiding this comment.
Did the math for this one as well, in MoP the formula becomes:
critFactor := (spell.CritDamageMultiplier() - 1) * critChance
averageMultiplier := (1.0 - missChance - dodgeChance - parryChance) * (1.0 + critFactor - blockChance * (critFactor + result.Target.BlockDamageReduction()))
where you'd omit the -= term on the next line from the original formula.
The intuitive explanation is that it's the same formula from Cata, except that the "effective Block chance" now gets suppressed by the total miss chance for the ability, since misses now prevent Block from even being rolled unlike before.
| table.BaseParryChance = UnitLevelFloat64(attacker.Level, 0, -0.002, -0.004, -0.006) | ||
| table.BaseSpellMissChance = UnitLevelFloat64(defender.Level, 0.06, 0.03, 0, 0) |
There was a problem hiding this comment.
It should be attacker.Level here similar to the expressions below.
| table.BaseMissChance = UnitLevelFloat64(attacker.Level, 0.03, 0.015, 0, 0) | ||
| table.BaseBlockChance = UnitLevelFloat64(attacker.Level, 0.03, 0.015, 0, -0.015) | ||
| table.BaseDodgeChance = UnitLevelFloat64(attacker.Level, 0.03, 0.015, 0, -0.015) | ||
| table.BaseParryChance = UnitLevelFloat64(attacker.Level, 0.03, 0.015, 0, -0.015) |
There was a problem hiding this comment.
Are you sure that +2 and +3 level enemies have a 0% chance to Miss with their autos? This seems odd, given that simc has specific logic coded to make auto-attack Miss outcomes not grant extra Vengeance and only refresh the existing Vengeance buff, unlike auto-attack Dodge and Parry outcomes which grant Vengeance as if they had landed. If boss auto Miss outcomes aren't even possible in the attack table, then they shouldn't have needed this branching logic.
There was a problem hiding this comment.
This was also from the GC posts. I can do more log digging to double check, but so far everything that was said in that thread has held true when verified.
The mechanics of Vengeance were also mentioned - I think the case of a Miss is just mechanically present, but will never occur on a 92+.
It does hold relevance for trash mobs in Challenge Modes, if SimC had any form of their DungeonSlice model then
There was a problem hiding this comment.
Some bosses appear to be flagged as Dual Wielding, and receive the very same +19% modifier to their miss chance. If we want to support that in an Encounter design, I will need to add in the negative values so the final miss rate is correct.
https://www.warcraftlogs.com/reports/aqYQNvtbV3L8CgmT?fight=3&type=damage-taken&source=22&ability=1&target=102
https://www.wowhead.com/mop-classic/npc=71479/he-softfoot
I've been otherwise unable to find any Miss hits from non-DW bosses (the rest of SoO, basically)
| if spell.Unit.PseudoStats.InFrontOfTarget { | ||
| if !result.applyAttackTableMissNoDWPenalty(spell, attackTable, roll, &chance) { | ||
| if result.applyAttackTableCritSeparateRoll(sim, spell, attackTable, countHits) { | ||
| result.applyAttackTableBlock(spell, attackTable, roll, &chance) | ||
| } else { | ||
| if !result.applyAttackTableBlock(spell, attackTable, roll, &chance) { | ||
| result.applyAttackTableHit(spell, countHits) | ||
| } | ||
| } | ||
| if !result.applyAttackTableMissNoDWPenalty(spell, attackTable, roll, &chance) && | ||
| !result.applyAttackTableDodge(spell, attackTable, roll, &chance) && | ||
| !result.applyAttackTableCritSeparateRoll(sim, spell, attackTable, countHits) { | ||
| result.applyAttackTableHit(spell, countHits) | ||
| } | ||
| } else { | ||
| if !result.applyAttackTableMissNoDWPenalty(spell, attackTable, roll, &chance) && |
There was a problem hiding this comment.
You also need to handle the Dodge outcome in the case where the Hunter is attacking from behind. In fact, now that Blocks are no longer possible for ranged attacks, I don't think you even need to branch off InFrontOfTarget anymore - it should be the same attack table in both cases.
| if dot.Spell.Unit.PseudoStats.InFrontOfTarget { | ||
| if !result.applyAttackTableMissNoDWPenalty(dot.Spell, attackTable, roll, &chance) { | ||
| if result.applyAttackTableCritSeparateRollSnapshot(sim, dot) { | ||
| result.applyAttackTableBlock(dot.Spell, attackTable, roll, &chance) | ||
| } else { | ||
| if !result.applyAttackTableBlock(dot.Spell, attackTable, roll, &chance) { | ||
| result.applyAttackTableHit(dot.Spell, countHits) | ||
| } | ||
| } | ||
| if !result.applyAttackTableMissNoDWPenalty(dot.Spell, attackTable, roll, &chance) && | ||
| !result.applyAttackTableDodge(dot.Spell, attackTable, roll, &chance) && | ||
| !result.applyAttackTableCritSeparateRollSnapshot(sim, dot) { | ||
| result.applyAttackTableHit(dot.Spell, countHits) | ||
| } | ||
| } else { | ||
| if !result.applyAttackTableMissNoDWPenalty(dot.Spell, attackTable, roll, &chance) && |
There was a problem hiding this comment.
Same here, you're missing Dodge outcomes when attacking from behind, and shouldn't need to branch off InFrontOfTarget anymore in MoP if I'm understanding the changes correctly.
| if spell.Unit.PseudoStats.InFrontOfTarget { | ||
| roll := sim.RandomFloat("White Hit Table") | ||
| chance := 0.0 | ||
|
|
||
| if result.applyAttackTableCritSeparateRoll(sim, spell, attackTable, countHits) { | ||
| result.applyAttackTableBlock(spell, attackTable, roll, &chance) | ||
| result.applyAttackTableBlock(sim, spell, attackTable) | ||
| } else { | ||
| if !result.applyAttackTableBlock(spell, attackTable, roll, &chance) { | ||
| if !result.applyAttackTableBlock(sim, spell, attackTable) { | ||
| result.applyAttackTableHit(spell, countHits) | ||
| } | ||
| } |
There was a problem hiding this comment.
Remove ranged Block outcomes here for consistency if you're removing them everywhere else, which also means that InFrontOfTarget should no longer matter.
| if sim.RandomFloat("Player Block") < chance { | ||
| result.Outcome |= OutcomeBlock | ||
| spell.SpellMetrics[result.Target.UnitIndex].Blocks++ |
There was a problem hiding this comment.
In MoP this also needs a branch for if result.DidCrit() like the player-vs-npc case, since CritBlock outcomes are now possible for enemy autos. It won't be relevant in practice since all tanks are Crit immune, but we might as well handle it correctly for completeness.
There was a problem hiding this comment.
I believe this should already be handled by the way outcomeEnemyMeleeWhite flows, in the exact same way Player->Target does. It checks Miss/Dodge/Parry, if none of those are true it then checks Crit. If it does Crit, then it rolls Block. If Block is true, it takes the Crit outcome and applies |= Block to the Outcome (as opposed to just setting it equal to, as the Crit/Miss/Dodge/Parry/Hit functions do)
There was a problem hiding this comment.
Ah I see, to add to SpellMetrics for -specifically- CritBlock. I'll add this
Spell Power/Damage updates



I'm kind of stupid so maybe missed something, but this should be right for updating Dodge/Parry to 7.5% and Spell Hit inheriting from Expertise and Hit Rating.
As far as I could tell looking through SimC and old posts, the logic for the rest of the table remained as-is.