Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
lologarithm committed May 4, 2022
2 parents 3600414 + 5e363f1 commit 8560e38
Show file tree
Hide file tree
Showing 16 changed files with 1,457 additions and 1,410 deletions.
136 changes: 0 additions & 136 deletions sim/druid/balance/balance.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package balance

import (
"time"

"github.com/wowsims/tbc/sim/common"
"github.com/wowsims/tbc/sim/core"
"github.com/wowsims/tbc/sim/core/proto"
Expand Down Expand Up @@ -65,143 +63,9 @@ func (moonkin *BalanceDruid) GetDruid() *druid.Druid {
return moonkin.Druid
}

func (moonkin *BalanceDruid) GetPresimOptions() *core.PresimOptions {
// If not adaptive, just use the primary rotation directly.
if moonkin.primaryRotation.PrimarySpell != proto.BalanceDruid_Rotation_Adaptive {
return nil
}

rotations := moonkin.GetDpsRotationHierarchy(moonkin.primaryRotation)
rotationIdx := 0

return &core.PresimOptions{
SetPresimPlayerOptions: func(player *proto.Player) {
*player.Spec.(*proto.Player_BalanceDruid).BalanceDruid.Rotation = rotations[rotationIdx]
},

OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool {
if float64(presimResult.SecondsOomAvg) <= 0.03*duration.Seconds() {
moonkin.primaryRotation = rotations[rotationIdx]

// If the highest dps rotation is fine, we dont need any adaptive logic.
if rotationIdx == 0 {
return true
}

moonkin.useSurplusRotation = true
moonkin.surplusRotation = rotations[rotationIdx-1]
moonkin.manaTracker = common.NewManaSpendingRateTracker()

return true
}

rotationIdx++
if rotationIdx == len(rotations) {
// If we are here than all of the rotations went oom. No adaptive logic needed, just use the lowest one.
moonkin.primaryRotation = rotations[len(rotations)-1]
return true
}

return false
},
}
}

func (moonkin *BalanceDruid) Reset(sim *core.Simulation) {
if moonkin.useSurplusRotation {
moonkin.manaTracker.Reset()
}
moonkin.Druid.Reset(sim)
}

func (moonkin *BalanceDruid) OnGCDReady(sim *core.Simulation) {
moonkin.tryUseGCD(sim)
}

func (moonkin *BalanceDruid) OnManaTick(sim *core.Simulation) {
if moonkin.FinishedWaitingForManaAndGCDReady(sim) {
moonkin.tryUseGCD(sim)
}
}

func (moonkin *BalanceDruid) tryUseGCD(sim *core.Simulation) {
if moonkin.useSurplusRotation {
moonkin.manaTracker.Update(sim, moonkin.GetCharacter())

// If we have enough mana to burn, use the surplus rotation.
if moonkin.manaTracker.ProjectedManaSurplus(sim, moonkin.GetCharacter()) {
moonkin.actRotation(sim, moonkin.surplusRotation)
} else {
moonkin.actRotation(sim, moonkin.primaryRotation)
}
} else {
moonkin.actRotation(sim, moonkin.primaryRotation)
}
}

func (moonkin *BalanceDruid) actRotation(sim *core.Simulation, rotation proto.BalanceDruid_Rotation) {
// Activate shared druid behaviors
// Use Rebirth at the beginning of the fight if flagged in rotation settings
// Potentially allow options for "Time of cast" in future or default cast like 1 min into fight
// Currently just casts at the beginning of encounter (with all CDs popped)
if moonkin.useBattleRes && moonkin.TryRebirth(sim) {
return
}

target := sim.GetPrimaryTarget()

var spell *core.Spell

if moonkin.ShouldCastFaerieFire(sim, target, rotation) {
spell = moonkin.FaerieFire
} else if moonkin.ShouldCastHurricane(sim, rotation) {
spell = moonkin.Hurricane
} else if moonkin.ShouldCastInsectSwarm(sim, target, rotation) {
spell = moonkin.InsectSwarm
} else if moonkin.ShouldCastMoonfire(sim, target, rotation) {
spell = moonkin.Moonfire
} else {
switch rotation.PrimarySpell {
case proto.BalanceDruid_Rotation_Starfire:
spell = moonkin.Starfire8
case proto.BalanceDruid_Rotation_Starfire6:
spell = moonkin.Starfire6
case proto.BalanceDruid_Rotation_Wrath:
spell = moonkin.Wrath
}
}

if success := spell.Cast(sim, target); !success {
moonkin.WaitForMana(sim, spell.CurCast.Cost)
}
}

// Returns the order of DPS rotations to try, from highest to lowest dps. The
// lower DPS rotations are more mana efficient.
//
// Rotation tiers, from highest dps to lowest:
// - SF8 + MF
// - SF6 + MF
// - SF6, or SF6 + IS if 4p T5 is worn.
func (moonkin *BalanceDruid) GetDpsRotationHierarchy(baseRotation proto.BalanceDruid_Rotation) []proto.BalanceDruid_Rotation {
rotations := []proto.BalanceDruid_Rotation{}

currentRotation := baseRotation
currentRotation.PrimarySpell = proto.BalanceDruid_Rotation_Starfire
currentRotation.Moonfire = true
rotations = append(rotations, currentRotation)

currentRotation.PrimarySpell = proto.BalanceDruid_Rotation_Starfire6
rotations = append(rotations, currentRotation)

if druid.ItemSetNordrassil.CharacterHasSetBonus(&moonkin.Character, 4) {
currentRotation.Moonfire = false
currentRotation.InsectSwarm = true
rotations = append(rotations, currentRotation)
} else {
currentRotation.Moonfire = false
rotations = append(rotations, currentRotation)
}

return rotations
}
144 changes: 144 additions & 0 deletions sim/druid/balance/rotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package balance

import (
"time"

"github.com/wowsims/tbc/sim/common"
"github.com/wowsims/tbc/sim/core"
"github.com/wowsims/tbc/sim/core/proto"
"github.com/wowsims/tbc/sim/druid"
)

func (moonkin *BalanceDruid) OnGCDReady(sim *core.Simulation) {
moonkin.tryUseGCD(sim)
}

func (moonkin *BalanceDruid) OnManaTick(sim *core.Simulation) {
if moonkin.FinishedWaitingForManaAndGCDReady(sim) {
moonkin.tryUseGCD(sim)
}
}

func (moonkin *BalanceDruid) tryUseGCD(sim *core.Simulation) {
if moonkin.useSurplusRotation {
moonkin.manaTracker.Update(sim, moonkin.GetCharacter())

// If we have enough mana to burn, use the surplus rotation.
if moonkin.manaTracker.ProjectedManaSurplus(sim, moonkin.GetCharacter()) {
moonkin.actRotation(sim, moonkin.surplusRotation)
} else {
moonkin.actRotation(sim, moonkin.primaryRotation)
}
} else {
moonkin.actRotation(sim, moonkin.primaryRotation)
}
}

func (moonkin *BalanceDruid) actRotation(sim *core.Simulation, rotation proto.BalanceDruid_Rotation) {
// Activate shared druid behaviors
// Use Rebirth at the beginning of the fight if flagged in rotation settings
// Potentially allow options for "Time of cast" in future or default cast like 1 min into fight
// Currently just casts at the beginning of encounter (with all CDs popped)
if moonkin.useBattleRes && moonkin.TryRebirth(sim) {
return
}

target := sim.GetPrimaryTarget()

var spell *core.Spell

if moonkin.ShouldCastFaerieFire(sim, target, rotation) {
spell = moonkin.FaerieFire
} else if moonkin.ShouldCastHurricane(sim, rotation) {
spell = moonkin.Hurricane
} else if moonkin.ShouldCastInsectSwarm(sim, target, rotation) {
spell = moonkin.InsectSwarm
} else if moonkin.ShouldCastMoonfire(sim, target, rotation) {
spell = moonkin.Moonfire
} else {
switch rotation.PrimarySpell {
case proto.BalanceDruid_Rotation_Starfire:
spell = moonkin.Starfire8
case proto.BalanceDruid_Rotation_Starfire6:
spell = moonkin.Starfire6
case proto.BalanceDruid_Rotation_Wrath:
spell = moonkin.Wrath
}
}

if success := spell.Cast(sim, target); !success {
moonkin.WaitForMana(sim, spell.CurCast.Cost)
}
}

// Returns the order of DPS rotations to try, from highest to lowest dps. The
// lower DPS rotations are more mana efficient.
//
// Rotation tiers, from highest dps to lowest:
// - SF8 + MF
// - SF6 + MF
// - SF6, or SF6 + IS if 4p T5 is worn.
func (moonkin *BalanceDruid) GetDpsRotationHierarchy(baseRotation proto.BalanceDruid_Rotation) []proto.BalanceDruid_Rotation {
rotations := []proto.BalanceDruid_Rotation{}

currentRotation := baseRotation
currentRotation.PrimarySpell = proto.BalanceDruid_Rotation_Starfire
currentRotation.Moonfire = true
rotations = append(rotations, currentRotation)

currentRotation.PrimarySpell = proto.BalanceDruid_Rotation_Starfire6
rotations = append(rotations, currentRotation)

if druid.ItemSetNordrassil.CharacterHasSetBonus(&moonkin.Character, 4) {
currentRotation.Moonfire = false
currentRotation.InsectSwarm = true
rotations = append(rotations, currentRotation)
} else {
currentRotation.Moonfire = false
rotations = append(rotations, currentRotation)
}

return rotations
}

func (moonkin *BalanceDruid) GetPresimOptions() *core.PresimOptions {
// If not adaptive, just use the primary rotation directly.
if moonkin.primaryRotation.PrimarySpell != proto.BalanceDruid_Rotation_Adaptive {
return nil
}

rotations := moonkin.GetDpsRotationHierarchy(moonkin.primaryRotation)
rotationIdx := 0

return &core.PresimOptions{
SetPresimPlayerOptions: func(player *proto.Player) {
*player.Spec.(*proto.Player_BalanceDruid).BalanceDruid.Rotation = rotations[rotationIdx]
},

OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool {
if float64(presimResult.SecondsOomAvg) <= 0.03*duration.Seconds() {
moonkin.primaryRotation = rotations[rotationIdx]

// If the highest dps rotation is fine, we dont need any adaptive logic.
if rotationIdx == 0 {
return true
}

moonkin.useSurplusRotation = true
moonkin.surplusRotation = rotations[rotationIdx-1]
moonkin.manaTracker = common.NewManaSpendingRateTracker()

return true
}

rotationIdx++
if rotationIdx == len(rotations) {
// If we are here than all of the rotations went oom. No adaptive logic needed, just use the lowest one.
moonkin.primaryRotation = rotations[len(rotations)-1]
return true
}

return false
},
}
}
Loading

0 comments on commit 8560e38

Please sign in to comment.