Skip to content

Commit

Permalink
Merge pull request #1195 from wowsims/guardian
Browse files Browse the repository at this point in the history
Baleroc AI Fixes
  • Loading branch information
NerdEgghead authored Nov 12, 2024
2 parents 4c3f888 + 2c230de commit 0c49560
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 72 deletions.
4 changes: 4 additions & 0 deletions sim/core/aura_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ func CreateDamageAbsorptionAura(character *Character, auraLabel string, actionID
FreshShieldStrengthCalculator: calculator,
}

aura.ApplyOnExpire(func(_ *Aura, _ *Simulation) {
aura.ShieldStrength = 0
})

character.AddDynamicDamageTakenModifier(func(sim *Simulation, spell *Spell, result *SpellResult) {
if aura.Aura.IsActive() && result.Damage > 0 && (extraSpellCheck == nil || extraSpellCheck(spell)) {
absorbedDamage := min(aura.ShieldStrength, result.Damage)
Expand Down
12 changes: 7 additions & 5 deletions sim/core/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,14 @@ func (character *Character) applyHealingModel(healingModel *proto.HealingModel)
// Use modeled HPS to scale heal per tick based on random cadence
healPerTick = healingModel.Hps * (float64(timeToNextHeal) / float64(time.Second)) * character.PseudoStats.HealingTakenMultiplier * character.PseudoStats.ExternalHealingTakenMultiplier

// Execute the direct portion of the heal
character.GainHealth(sim, healPerTick * (1.0 - absorbFrac), healthMetrics)
if healPerTick > 0 {
// Execute the direct portion of the heal
character.GainHealth(sim, healPerTick * (1.0 - absorbFrac), healthMetrics)

// Turn the remainder into an absorb shield
if absorbShield != nil {
absorbShield.Activate(sim)
// Turn the remainder into an absorb shield
if absorbShield != nil {
absorbShield.Activate(sim)
}
}

// Might use this again in the future to track "absorb" metrics but currently disabled
Expand Down
156 changes: 90 additions & 66 deletions sim/encounters/firelands/baleroc_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (ai *BalerocAI) Initialize(target *core.Target, config *proto.Target) {

if ai.stackCountForFirstSwap <= 0 {
target.CurrentTarget = ai.MainTank
target.SecondaryTarget = ai.OffTank
}

ai.initialHealerStackGain = config.TargetInputs[2].NumberValue
Expand Down Expand Up @@ -205,6 +206,10 @@ func (ai *BalerocAI) registerBlazeOfGlory() {
hpDepByStackCount[i] = tankUnit.NewDynamicMultiplyStat(stats.Health, 1.0 + 0.2*float64(i))
}

// Blaze of Glory applications also heal the player, just like
// most other temporary max health increases.
healthMetrics := tankUnit.NewHealthMetrics(blazeOfGloryActionID)

tankUnit.GetOrRegisterAura(core.Aura{
Label: "Blaze of Glory",
ActionID: blazeOfGloryActionID,
Expand All @@ -214,13 +219,22 @@ func (ai *BalerocAI) registerBlazeOfGlory() {
OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) {
aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexPhysical] *= (1.0 + 0.2*float64(newStacks)) / (1.0 + 0.2*float64(oldStacks))

// Cache max HP prior to processing multipliers.
oldMaxHp := aura.Unit.MaxHealth()

if oldStacks > 0 {
aura.Unit.DisableDynamicStatDep(sim, hpDepByStackCount[oldStacks])
}

if newStacks > 0 {
aura.Unit.EnableDynamicStatDep(sim, hpDepByStackCount[newStacks])
}

hpGain := aura.Unit.MaxHealth() - oldMaxHp

if hpGain > 0 {
aura.Unit.GainHealth(sim, hpGain, healthMetrics)
}
},
})
}
Expand Down Expand Up @@ -254,37 +268,11 @@ func (ai *BalerocAI) registerBlazeOfGlory() {
}

func (ai *BalerocAI) registerBlades() {
// 0 - 10N, 1 - 25N, 2 - 10H, 3 - 25H
scalingIndex := core.TernaryInt(ai.raidSize == 10, core.TernaryInt(ai.isHeroic, 2, 0), core.TernaryInt(ai.isHeroic, 3, 1))

// https://wago.tools/db2/SpellEffect?build=4.4.1.57294&filter[SpellID]=99351&page=1&sort[SpellID]=asc
infernoStrikeBase := []float64{97499, 165749, 136499, 232049}[scalingIndex]
infernoStrikeVariance := []float64{5000, 8500, 7000, 11900}[scalingIndex]

infernoStrike := ai.Target.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: 99351},
SpellSchool: core.SpellSchoolFire,
ProcMask: core.ProcMaskSpellDamage,
Flags: core.SpellFlagMeleeMetrics,
DamageMultiplier: 1,

ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) {
damageRoll := infernoStrikeBase + infernoStrikeVariance*sim.RandomFloat("Inferno Strike Damage")
spell.CalcAndDealDamage(sim, target, damageRoll, spell.OutcomeEnemyMeleeWhite)
},
})

// First register the blade auras and activation spells.
const bladeDuration = time.Second * 15
const bladeCooldown = time.Second * 45 // very first one is special cased as 30s
const bladeCastTime = time.Millisecond * 1500

infernoBladeActionID := core.ActionID{SpellID: 99350}
infernoBladeAura := ai.Target.RegisterAura(core.Aura{
Label: "Inferno Blade",
ActionID: infernoBladeActionID,
Duration: bladeDuration,
})

sharedBladeCastHandler := func(sim *core.Simulation) {
// First, schedule a swing timer reset to fire on cast completion.
ai.Target.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime + bladeCastTime, true)
Expand All @@ -299,6 +287,13 @@ func (ai *BalerocAI) registerBlades() {
ai.blazeOfGlory.CD.Set(sim.CurrentTime + ai.blazeOfGlory.CD.Duration)
}

infernoBladeActionID := core.ActionID{SpellID: 99350}
infernoBladeAura := ai.Target.RegisterAura(core.Aura{
Label: "Inferno Blade",
ActionID: infernoBladeActionID,
Duration: bladeDuration,
})

ai.infernoBlade = ai.Target.RegisterSpell(core.SpellConfig{
ActionID: infernoBladeActionID,
ProcMask: core.ProcMaskEmpty,
Expand Down Expand Up @@ -327,52 +322,14 @@ func (ai *BalerocAI) registerBlades() {
},
})

decimatingStrikeActionID := core.ActionID{SpellID: 99353}
decimatingStrikeDebuffConfig := core.Aura{
Label: "Decimating Strike",
ActionID: decimatingStrikeActionID,
Duration: time.Second * 4,

OnGain: func(aura *core.Aura, _ *core.Simulation) {
aura.Unit.PseudoStats.HealingDealtMultiplier *= 0.1
},

OnExpire: func(aura *core.Aura, _ *core.Simulation) {
aura.Unit.PseudoStats.HealingDealtMultiplier /= 0.1
},
}

for _, tankUnit := range []*core.Unit{ai.MainTank, ai.OffTank} {
if tankUnit != nil {
tankUnit.GetOrRegisterAura(decimatingStrikeDebuffConfig)
}
}

decimatingStrike := ai.Target.RegisterSpell(core.SpellConfig{
ActionID: decimatingStrikeActionID,
SpellSchool: core.SpellSchoolShadow,
ProcMask: core.ProcMaskSpellDamage,
Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIgnoreModifiers | core.SpellFlagIgnoreResists,
DamageMultiplier: 1,

ApplyEffects: func(sim *core.Simulation, tankTarget *core.Unit, spell *core.Spell) {
spell.CalcAndDealDamage(sim, tankTarget, max(0.9 * tankTarget.MaxHealth(), 250000), spell.OutcomeEnemyMeleeWhite)
debuffAura := tankTarget.GetAuraByID(decimatingStrikeActionID)

if debuffAura != nil {
debuffAura.Activate(sim)
}
},
})

decimationBladeActionID := core.ActionID{SpellID: 99352}
decimationBladeAura := ai.Target.RegisterAura(core.Aura{
Label: "Decimation Blade",
ActionID: decimationBladeActionID,
Duration: bladeDuration,

OnExpire: func(_ *core.Aura, sim *core.Simulation) {
if ai.tankSwap {
if ai.tankSwap && (ai.Target.CurrentTarget == ai.OffTank) {
ai.swapTargets(sim, ai.MainTank)
}
},
Expand Down Expand Up @@ -410,6 +367,73 @@ func (ai *BalerocAI) registerBlades() {
},
})

// Then register the strikes that replace boss melees during each blade.
// 0 - 10N, 1 - 25N, 2 - 10H, 3 - 25H
scalingIndex := core.TernaryInt(ai.raidSize == 10, core.TernaryInt(ai.isHeroic, 2, 0), core.TernaryInt(ai.isHeroic, 3, 1))

// https://wago.tools/db2/SpellEffect?build=4.4.1.57294&filter[SpellID]=99351&page=1&sort[SpellID]=asc
infernoStrikeBase := []float64{97499, 165749, 136499, 232049}[scalingIndex]
infernoStrikeVariance := []float64{5000, 8500, 7000, 11900}[scalingIndex]

infernoStrike := ai.Target.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: 99351},
SpellSchool: core.SpellSchoolFire,
ProcMask: core.ProcMaskSpellDamage,
Flags: core.SpellFlagMeleeMetrics,
DamageMultiplier: 1,

ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) {
damageRoll := infernoStrikeBase + infernoStrikeVariance*sim.RandomFloat("Inferno Strike Damage")
spell.CalcAndDealDamage(sim, target, damageRoll, spell.OutcomeEnemyMeleeWhite)
},
})

decimatingStrikeActionID := core.ActionID{SpellID: 99353}
decimatingStrikeDebuffConfig := core.Aura{
Label: "Decimating Strike",
ActionID: decimatingStrikeActionID,
Duration: time.Second * 4,

OnGain: func(aura *core.Aura, _ *core.Simulation) {
aura.Unit.PseudoStats.HealingDealtMultiplier *= 0.1
},

OnExpire: func(aura *core.Aura, _ *core.Simulation) {
aura.Unit.PseudoStats.HealingDealtMultiplier /= 0.1
},
}

for _, tankUnit := range []*core.Unit{ai.MainTank, ai.OffTank} {
if tankUnit != nil {
tankUnit.GetOrRegisterAura(decimatingStrikeDebuffConfig)
}
}

decimatingStrike := ai.Target.RegisterSpell(core.SpellConfig{
ActionID: decimatingStrikeActionID,
SpellSchool: core.SpellSchoolShadow,
ProcMask: core.ProcMaskSpellDamage,
Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIgnoreModifiers | core.SpellFlagIgnoreResists,
DamageMultiplier: 1,

ApplyEffects: func(sim *core.Simulation, tankTarget *core.Unit, spell *core.Spell) {
result := spell.CalcAndDealDamage(sim, tankTarget, max(0.9 * tankTarget.MaxHealth(), 250000), spell.OutcomeEnemyMeleeWhite)

if result.Landed() {
debuffAura := tankTarget.GetAuraByID(decimatingStrikeActionID)

if debuffAura != nil {
debuffAura.Activate(sim)
}
}

// MT should taunt as soon as the final Decimating Strike goes out in order to maximize their Blaze of Glory stack count.
if ai.tankSwap && (ai.stackCountForFirstSwap > 0) && (decimationBladeAura.ExpiresAt() < ai.Target.AutoAttacks.NextAttackAt()) {
ai.swapTargets(sim, ai.MainTank)
}
},
})

ai.Target.AutoAttacks.SetReplaceMHSwing(func(_ *core.Simulation, mhSwingSpell *core.Spell) *core.Spell {
if infernoBladeAura.IsActive() {
return infernoStrike
Expand Down
2 changes: 1 addition & 1 deletion ui/druid/guardian/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,6 @@ export const PRESET_BUILD_BALEROC_OT = PresetUtils.makePresetBuild("Baleroc OT",
rotation: ROTATION_BALEROC_OT,
encounter: PresetUtils.makePresetEncounter(
"Baleroc OT",
'http://localhost:5173/cata/druid/guardian/?i=cmxe#eJzVUr9PFEEUvtlDc4gmB5oIJJIHlV4QTyJGickuF4NHAvEixGDn3O7s3eRmZ8/ZWS5HRayMsTB00CiV0creWKuJJFQGGysKCk2sjHa+neUQMP4BvmIzO/O9H9/3vRNncwSIQ+pkhZCnhKxY5LVFtixS6s6Ta6RMtgmZzExaedKXGXywnj1eEbTNVK4nT0ZO5bLFgYq1aN0/hpmlzKtsz2B3b8bEmPPe6tq1vJ8W2cimV/ecx9lL3eY4+83pXV9L4pd9IT18ti+nh5f2QIrfteH4yLPucjbNKS45e7XPOAMp9IN9Pb3Zsod9E5/sqc2PSXy1x3dyzR8b2eWTJSqYCl0Yn4AyLPZ3FbZJ5j+MFe+dc+D3/MPKi6mjGCPHzpvTU2vkSu/VG2/vfLE7GKdMimSVPCF9QwtUNiBq0Sb4oYKbzOUB1TyUUBLUY4X6jA9unbkN5o2CrjOohlEELS4EVBloTGYeVNtgyoyDoohRCKTSoOcol+mbFysua381wFLSC1vRWD/O8yibI30wI7nmVMDthQSyzCD04ZYIVRsiTd1GVNi2cCgZyovLaOVop7WZiZtczUzzZow3sdRcmN+oib19jvOaOuCG+AY8Auq6cRALTPPGYJaqGjJYoiJm+CRE2DLZOI0OocaogiBUDGitplgU8SUm2tCqc4ENdDJCwJhOmCZJ4xPFBpQreEZsPRSeETl58bmK9L/UGIVqrCGgDQaC+yxt6HHf524s9H6NuYWOqjPSZ0qGRySFhTqy47IZG5q8JrGOB9w3vhnTkaEpHdGqQPbD6Xp8t1fJJkEvznW8uMs1fuebVDXQ46CZGM0Kz8n0IToBl7E2fnXkT3cm4nvyQp1RkQxMawy1N5b9UR+xOGZAZftQv9R1QI3oElOYiZunW4zJPdS0oAEDxfxEY2aASdOEY4fQkLNKXORTmNeMemaRsN3BJvtTJF4YcoV5tB43IfnZl1yxADfaw+sDJDtd8s5vNoJvHA=='
'http://localhost:5173/cata/druid/guardian/?i=rcmxe#eJzVUk9oHFUY3zcz2c6+JGUztSQZsL7sQeKShG3aFA3F3QSJG0kxmBDizZeZN7uvOzuzzMxmSU6xHhqLhxIQbRG1J6EnCYIY0YMXFVpILyXtwYr00IMFQZD0In7vTSbdre3Bo++wO9/3/b4/v9/34X4dEVRCVbSB0GWENhT0pYJ2FTSdyaKXURntITSZmlSyyEiZl9NX1fS8S9dYoHdnUa5XVwqD88qy8k4XpE6nrqvdZqYvJd9Y6SdF21XUu0ri+bX4m/Jh14Ca6zYeu3AfTpsaVgb6ckdwF1ZHT4b4ONbPp7GmP9rXchkM7tHC2MnQ0ExlMmUMmv24N9eNM9tIYL66mDbF/8f31GeFPtlGceiShrGSVQxAb8ahHQgtm0u4bMzgnnFsQBS8ty4q2MwP6Mjo/QG1OU2YD6NTMqYZPefbQ1ANK6dDE4P9zZ+aiBgl81VcMMdwNncU92yjjISr+oV0UmGnzRdXOBNXuPMekhWGzBfwsbtKVqIU3TpsowtLQrCpSyqfvq8aGfOImFF/OHzo/vvdLmPOfAOfNSbjOTpYtqkF+bg/f1zvMjLXE0fc7lQ8E+Blw6Nmj7Rvb3ba39/TOuyEQ9V08LKx9B/0zZvD7ZN9va/JyTQjs5M4nqq3yBN11afX7WDRa8KpglQfTQjzgWLvK+iaGl/l26VL6osZ+Tn3sNR39Yp4j4ovxR93ioMx7EGRpHOfZcpqDC2slg6u+rnSYAz9ufhK7NktDjny3S5O3bwh3u/F8ft6469r6nrPNHVZ4FtkfIKUyfKAlt9Dqf/h27B/LLWZwxfmv5h6EiPluP/tsakr6HTfmbPfvfVLMcGUyqiAttAHyDixSL0aCVu0QRw/IK8xi9dpxH2PTLvUZvnqrEOsKrNqzB4hUZWRFT8MSYu7LllhJIJkZpOVNSLLjJOAAiYAIPUk+hzlXhyzmwH3Kv9qAKU822+FYwMwz6YKZ0pmPR5x6pI3FwVknRHfIa+7frBGwohatTC/p8BQnu+NrsMqR5LWciYucyMmmzea4Gl6EXelGTagt8NhXlmHWD7ECA8JtaxmvelCmj1G5mhQAQar1G0yCLmu35LZME3kkwqjAan7ASO0UglYGPJV5q6RVpW70CASI9QZiwRTkTQ+UaiR8jx8A7bqu7YUWUQcHoTRs9QYISvNiNRpjRGXOyxuaHPH4VbTjQ5rnFtMVJ31HBZ4/hOSksUqsONeoylp8ooHdWzCHbk3uXRgKEuHdMUF9kPxefxR3EI3Eezi+WQXSzyC34UGDWqw43pDLJrlP0czHXTq3GtGcl+J/PHNhPxAXlJl1BUD0woD7eXKHqsPWBizTr21jn7x1gloRFdZAJlweVGLMe8ANePSOiMBc4TGTAJFU8ExIXSitIUs4JNfiBi15SFBu/Ymh1OIXUhy+QVYPVyCMA4lD1gdLtoGdxvJpEu29A+pJMjo'
),
});

0 comments on commit 0c49560

Please sign in to comment.