diff --git a/proto/api.proto b/proto/api.proto index f8def4fab5..bccfb2ea99 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -145,7 +145,7 @@ message ActionMetrics { bool is_passive = 5; } -// Metrics for a specific action, when cast at a particular target. +// Metrics for a specific action, when cast at a particular target. Next = 37 message TargetedActionMetrics { reserved 19, 20; reserved "crit_block_damage", "crit_blocks"; @@ -195,6 +195,9 @@ message TargetedActionMetrics { // # of times this action was a Blocked critical strike. int32 blocked_crits = 33; + // # of times this action was a Crush. + int32 crushes = 35; + // # of times this action was a Glance. int32 glances = 8; @@ -225,6 +228,9 @@ message TargetedActionMetrics { // Total glancing damage done to this target by this action. double glance_damage = 17; + // Total crushing damage done to this target by this action. + double crush_damage = 36; + // Total block damage done to this target by this action. double block_damage = 18; diff --git a/sim/core/metrics_aggregator.go b/sim/core/metrics_aggregator.go index b78ec56cb1..211a7018d4 100644 --- a/sim/core/metrics_aggregator.go +++ b/sim/core/metrics_aggregator.go @@ -173,6 +173,7 @@ type SpellMetrics struct { TotalGlanceDamage float64 // Damage done by all glance casts of this spell. TotalBlockDamage float64 // Damage done by all block casts of this spell. TotalBlockedCritDamage float64 // Damage done by all blocked critical casts casts of this spell. + TotalCrushDamage float64 // Damage done by all crushed casts of this spell. TotalThreat float64 // Threat generated by all casts of this spell. TotalHealing float64 // Healing done by all casts of this spell. TotalCritHealing float64 // Healing done by all critical casts of this spell. @@ -196,6 +197,7 @@ type TargetedActionMetrics struct { Parries int32 Blocks int32 BlockedCrits int32 + Crushes int32 Damage float64 ResistedDamage float64 @@ -208,6 +210,7 @@ type TargetedActionMetrics struct { GlanceDamage float64 BlockDamage float64 BlockedCritDamage float64 + CrushDamage float64 Threat float64 Healing float64 CritHealing float64 @@ -234,6 +237,7 @@ func (tam *TargetedActionMetrics) ToProto(unitIndex int32) *proto.TargetedAction Parries: tam.Parries, Blocks: tam.Blocks, BlockedCrits: tam.BlockedCrits, + Crushes: tam.Crushes, Damage: tam.Damage, ResistedDamage: tam.ResistedDamage, CritDamage: tam.CritDamage, @@ -245,6 +249,7 @@ func (tam *TargetedActionMetrics) ToProto(unitIndex int32) *proto.TargetedAction GlanceDamage: tam.GlanceDamage, BlockDamage: tam.BlockDamage, BlockedCritDamage: tam.BlockedCritDamage, + CrushDamage: tam.CrushDamage, Threat: tam.Threat, Healing: tam.Healing, CritHealing: tam.CritHealing, @@ -391,6 +396,7 @@ func (unitMetrics *UnitMetrics) addSpellMetrics(spell *Spell, actionID ActionID, tam.Parries += spellTargetMetrics.Parries tam.Blocks += spellTargetMetrics.Blocks tam.BlockedCrits += spellTargetMetrics.BlockedCrits + tam.Crushes += spellTargetMetrics.Crushes tam.Glances += spellTargetMetrics.Glances tam.Damage += spellTargetMetrics.TotalDamage tam.ResistedDamage += spellTargetMetrics.TotalResistedDamage @@ -403,6 +409,7 @@ func (unitMetrics *UnitMetrics) addSpellMetrics(spell *Spell, actionID ActionID, tam.GlanceDamage += spellTargetMetrics.TotalGlanceDamage tam.BlockDamage += spellTargetMetrics.TotalBlockDamage tam.BlockedCritDamage += spellTargetMetrics.TotalBlockedCritDamage + tam.CrushDamage += spellTargetMetrics.TotalCrushDamage tam.Threat += spellTargetMetrics.TotalThreat tam.Healing += spellTargetMetrics.TotalHealing tam.CritHealing += spellTargetMetrics.TotalCritHealing diff --git a/sim/core/spell_outcome.go b/sim/core/spell_outcome.go index 0c358c234a..b1f39bf79b 100644 --- a/sim/core/spell_outcome.go +++ b/sim/core/spell_outcome.go @@ -641,7 +641,9 @@ func (spell *Spell) outcomeEnemyMeleeWhite(sim *Simulation, result *SpellResult, } if didHit && !result.applyEnemyAttackTableCrit(spell, attackTable, roll, &chance, countHits) { - result.applyAttackTableHit(spell, countHits) + if didHit && !result.applyEnemyAttackTableCrush(spell, attackTable, roll, &chance, countHits){ + result.applyAttackTableHit(spell, countHits) + } } } @@ -916,6 +918,25 @@ func (result *SpellResult) applyEnemyAttackTableCrit(spell *Spell, at *AttackTab return false } +func (result *SpellResult) applyEnemyAttackTableCrush(spell *Spell, at *AttackTable, roll float64, chance *float64, countHits bool) bool { + if !at.Attacker.PseudoStats.CanCrush { + return false + } + + crushChance := at.BaseCrushChance + *chance += max(0, crushChance) + + if roll < *chance { + result.Outcome = OutcomeCrush + if countHits { + spell.SpellMetrics[result.Target.UnitIndex].Crushes++ + } + result.Damage *= 1.5 + return true + } + return false +} + func (spell *Spell) OutcomeExpectedTick(_ *Simulation, _ *SpellResult, _ *AttackTable) { // result.Damage *= 1 } diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index 902a7bc249..e19abb637e 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -51,6 +51,10 @@ func (result *SpellResult) DidGlance() bool { return result.Outcome.Matches(OutcomeGlance) } +func (result *SpellResult) DidCrush() bool { + return result.Outcome.Matches(OutcomeCrush) +} + func (result *SpellResult) DidBlock() bool { return result.Outcome.Matches(OutcomeBlock) } @@ -418,6 +422,8 @@ func (spell *Spell) dealDamageInternal(sim *Simulation, isPeriodic bool, result spell.SpellMetrics[result.Target.UnitIndex].TotalGlanceDamage += result.Damage } else if result.DidBlock() { spell.SpellMetrics[result.Target.UnitIndex].TotalBlockDamage += result.Damage + } else if result.DidCrush() { + spell.SpellMetrics[result.Target.UnitIndex].TotalCrushDamage += result.Damage } spell.SpellMetrics[result.Target.UnitIndex].TotalThreat += result.Threat } diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 4f4dbf8355..6116f28c2b 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -471,6 +471,7 @@ type PseudoStats struct { CanBlock bool CanParry bool + CanCrush bool Stunned bool // prevents blocks, dodges, and parries ParryHaste bool diff --git a/sim/core/target.go b/sim/core/target.go index b8669c0856..61dd639873 100644 --- a/sim/core/target.go +++ b/sim/core/target.go @@ -3,7 +3,6 @@ package core import ( "strconv" "time" - "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/core/stats" ) @@ -143,6 +142,7 @@ func NewTarget(options *proto.Target, targetIndex int32) *Target { target.PseudoStats.CanBlock = true target.PseudoStats.CanParry = true + target.PseudoStats.CanCrush = true target.PseudoStats.ParryHaste = options.ParryHaste target.PseudoStats.InFrontOfTarget = true target.PseudoStats.DamageSpread = options.DamageSpread @@ -264,6 +264,7 @@ type AttackTable struct { BaseParryChance float64 BaseGlanceChance float64 BaseCritChance float64 + BaseCrushChance float64 GlanceMultiplierMin float64 GlanceMultiplierMax float64 @@ -359,7 +360,8 @@ func NewAttackTable(attacker *Unit, defender *Unit, weapon *Item) *AttackTable { } else { table.BaseBlockChance = 0 } - + + table.BaseCrushChance = UnitLevelFloat64(attacker.Level-defender.Level, 0.0, 0.0, 0.0, 0.15) table.BaseMissChance = 0.05 + levelDelta table.BaseDodgeChance = levelDelta // base dodge applied with class base stats table.BaseCritChance = 0.05 - levelDelta diff --git a/ui/core/components/detailed_results/dtps_metrics.tsx b/ui/core/components/detailed_results/dtps_metrics.tsx index deac4ddc0e..e8543c24f1 100644 --- a/ui/core/components/detailed_results/dtps_metrics.tsx +++ b/ui/core/components/detailed_results/dtps_metrics.tsx @@ -48,6 +48,7 @@ export class DtpsMetricsTable extends MetricsTable { const critTickValues = metric.damageDone.critTick; const glanceValues = metric.damageDone.glance; const blockValues = metric.damageDone.block; + const crushValues = metric.damageDone.crush; cellElem.appendChild( { name: 'Blocked Hit', ...blockValues, }, + { + name: 'Crushing Blow', + ...crushValues, + }, ], }, ]} @@ -127,6 +132,11 @@ export class DtpsMetricsTable extends MetricsTable { value: metric.dodges, percentage: metric.dodgePercent, }, + { + name: 'Crushing Blow', + value: metric.crushes, + percentage: metric.crushPercent, + }, ], }, ]} @@ -168,6 +178,7 @@ export class DtpsMetricsTable extends MetricsTable { const relativeCritTickPercent = (metric.critTicks / metric.landedTicks) * 100; const relativeGlancePercent = (metric.glances / metric.landedHits) * 100; const relativeBlockPercent = (metric.blocks / metric.landedHits) * 100; + const relativeCrushPercent = (metric.crushes / metric.landedHits) * 100; cellElem.appendChild( { value: metric.glances, percentage: relativeGlancePercent, }, + { + name: 'Crushing Blow', + value: metric.crushes, + percentage: relativeCrushPercent, + }, { name: 'Blocked Hit', value: metric.blocks, diff --git a/ui/core/proto_utils/sim_result.ts b/ui/core/proto_utils/sim_result.ts index b4c617b7e5..5ad515fc82 100644 --- a/ui/core/proto_utils/sim_result.ts +++ b/ui/core/proto_utils/sim_result.ts @@ -811,7 +811,8 @@ export class ActionMetrics { this.avgCritTickDamage - this.avgGlanceDamage - this.avgBlockDamage - - this.avgBlockedCritDamage + this.avgBlockedCritDamage - + this.avgCrushDamage ); } @@ -882,6 +883,14 @@ export class ActionMetrics { return this.combinedMetrics.blockDamage; } + get crushDamage() { + return this.combinedMetrics.crushDamage; + } + + get avgCrushDamage() { + return this.combinedMetrics.avgCrushDamage; + } + get avgBlockDamage() { return this.combinedMetrics.avgBlockDamage; } @@ -1115,6 +1124,14 @@ export class ActionMetrics { return this.combinedMetrics.resistedCritTickPercent; } + get crushes() { + return this.combinedMetrics.crushes; + } + + get crushPercent() { + return this.combinedMetrics.crushPercent; + } + get blocks() { return this.combinedMetrics.blocks; } @@ -1176,7 +1193,8 @@ export class ActionMetrics { this.avgTickDamage - this.avgGlanceDamage - this.avgBlockDamage - - this.avgBlockedCritDamage + this.avgBlockedCritDamage - + this.avgCrushDamage ).toFixed(8), ); const normalResistedHitAvgDamage = Number( @@ -1247,6 +1265,11 @@ export class ActionMetrics { percentage: (this.avgBlockedCritDamage / this.avgDamage) * 100, average: this.avgBlockedCritDamage / this.blockedCrits, }, + crush: { + value: this.avgCrushDamage, + percentage: (this.avgCrushDamage / this.avgDamage) * 100, + average: this.avgCrushDamage / this.crushes, + }, }; } @@ -1333,11 +1356,18 @@ export class TargetedActionMetrics { this.duration = duration; this.data = data; - this.landedHitsRaw = this.data.hits + this.data.crits + this.data.blocks + this.data.blockedCrits + this.data.glances; + this.landedHitsRaw = this.data.hits + this.data.crits + this.data.blocks + this.data.blockedCrits + this.data.glances + this.data.crushes; this.landedTicksRaw = this.data.ticks + this.data.critTicks; this.hitAttempts = - this.data.misses + this.data.dodges + this.data.parries + this.data.blocks + this.data.blockedCrits + this.data.glances + this.data.crits; + this.data.misses + + this.data.dodges + + this.data.parries + + this.data.blocks + + this.data.blockedCrits + + this.data.glances + + this.data.crits + + this.data.crushes; if (this.data.hits != 0) { this.hitAttempts += this.data.hits; @@ -1418,6 +1448,14 @@ export class TargetedActionMetrics { return this.data.glanceDamage / this.iterations; } + get crushDamage() { + return this.data.crushDamage; + } + + get avgCrushDamage() { + return this.data.crushDamage / this.iterations; + } + get blockDamage() { return this.data.blockDamage; } @@ -1638,6 +1676,14 @@ export class TargetedActionMetrics { return this.data.blocks / this.iterations; } + get crushes() { + return this.data.crushes / this.iterations; + } + + get crushPercent() { + return (this.data.crushes / this.hitAttempts) * 100; + } + get blockPercent() { return (this.data.blocks / this.hitAttempts) * 100; } @@ -1701,6 +1747,7 @@ export class TargetedActionMetrics { dodges: sum(actions.map(a => a.data.dodges)), parries: sum(actions.map(a => a.data.parries)), blocks: sum(actions.map(a => a.data.blocks)), + crushes: sum(actions.map(a => a.data.crushes)), blockedCrits: sum(actions.map(a => a.data.blockedCrits)), glances: sum(actions.map(a => a.data.glances)), damage: sum(actions.map(a => a.data.damage)), @@ -1714,6 +1761,7 @@ export class TargetedActionMetrics { glanceDamage: sum(actions.map(a => a.data.glanceDamage)), blockDamage: sum(actions.map(a => a.data.blockDamage)), blockedCritDamage: sum(actions.map(a => a.data.blockedCritDamage)), + crushDamage: sum(actions.map(a => a.data.crushDamage)), threat: sum(actions.map(a => a.data.threat)), healing: sum(actions.map(a => a.data.healing)), critHealing: sum(actions.map(a => a.data.critHealing)),