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

Baleroc AI Fixes #1195

Merged
merged 8 commits into from
Nov 12, 2024
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'
),
});
Loading