diff --git a/proto/common.proto b/proto/common.proto index 6c03e97e6a..6602de2eda 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -809,34 +809,37 @@ message TargetInput { } message Target { - // The in-game NPC ID. - int32 id = 14; - string name = 15; - - int32 level = 4; - MobType mob_type = 3; - repeated double stats = 5; - - // Auto attack parameters. - double min_base_damage = 7; - double damage_spread = 19; // replaces tight_enemy_damage - double swing_speed = 8; - bool dual_wield = 9; - bool dual_wield_penalty = 10; - bool parry_haste = 12; - bool suppress_dodge = 16; // Sunwell Radiance - SpellSchool spell_school = 13; // Allows elemental attacks. - - // Index in Raid.tanks indicating the player tanking this mob at the - // start of each pull. - // -1 or invalid index indicates not being tanked. - int32 tank_index = 6; - - // Used in tank swap AIs. - int32 second_tank_index = 100; - - // Custom Target AI parameters - repeated TargetInput target_inputs = 18; + // The in-game NPC ID. + int32 id = 14; + string name = 15; + + int32 level = 4; + MobType mob_type = 3; + repeated double stats = 5; + + // Auto attack parameters. + double min_base_damage = 7; + double damage_spread = 19; // replaces tight_enemy_damage + double swing_speed = 8; + bool dual_wield = 9; + bool dual_wield_penalty = 10; + bool parry_haste = 12; + bool suppress_dodge = 16; // Sunwell Radiance + SpellSchool spell_school = 13; // Allows elemental attacks. + + // Index in Raid.tanks indicating the player tanking this mob at the + // start of each pull. + // -1 or invalid index indicates not being tanked. + int32 tank_index = 6; + + // Used in tank swap AIs. + int32 second_tank_index = 100; + + // Used in dynamic target AIs. + bool disabled_at_start = 101; + + // Custom Target AI parameters + repeated TargetInput target_inputs = 18; } message Encounter { diff --git a/sim/common/cata/other_effects.go b/sim/common/cata/other_effects.go index 9689f86917..7b6a019783 100644 --- a/sim/common/cata/other_effects.go +++ b/sim/common/cata/other_effects.go @@ -44,7 +44,7 @@ func init() { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) if sim.Proc(0.1, "Vengeful Wisp") { // select random proc target - spreadTarget := sim.Encounter.TargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.TargetUnits))))] + spreadTarget := sim.Encounter.ActiveTargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.ActiveTargetUnits))))] // refresh dot on next step - refreshing potentially on aura expire // which will cause nasty things to happen @@ -85,7 +85,7 @@ func init() { if sim.Proc(0.1, "Vengeful Wisp") { // select random proc target - spreadTarget := sim.Encounter.TargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.TargetUnits))))] + spreadTarget := sim.Encounter.ActiveTargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.ActiveTargetUnits))))] spreadDot.Dot(spreadTarget).Apply(sim) // refresh self on } }, @@ -149,7 +149,7 @@ func init() { }, }, ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, storedMana, spell.OutcomeMagicHitAndCrit) } character.AddMana(sim, storedMana, manaMetric) @@ -947,8 +947,6 @@ func init() { fetishItemID := []int32{77982, 77210, 78002}[version] core.NewItemEffect(fetishItemID, func(agent core.Agent, _ proto.ItemLevelState) { character := agent.GetCharacter() - numTargets := character.Env.GetNumTargets() - actionID := core.ActionID{SpellID: []int32{109753, 107998, 109755}[version]} minDmg := []float64{8029, 9063, 10230}[version] maxDmg := []float64{12044, 13594, 15345}[version] @@ -965,12 +963,13 @@ func init() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + numTargets := sim.Environment.ActiveTargetCount() results := make([]*core.SpellResult, numTargets) - for idx := int32(0); idx < numTargets; idx++ { + for idx, target := range sim.Environment.GetActiveTargets() { baseDamage := sim.Roll(minDmg, maxDmg) + apMod*spell.MeleeAttackPower() - results[idx] = spell.CalcDamage(sim, sim.Environment.GetTargetUnit(idx), baseDamage, spell.OutcomeMeleeSpecialCritOnly) + results[idx] = spell.CalcDamage(sim, &target.Unit, baseDamage, spell.OutcomeMeleeSpecialCritOnly) } for idx := int32(0); idx < numTargets; idx++ { @@ -999,8 +998,6 @@ func init() { cunningItemID := []int32{77980, 77208, 78000}[version] core.NewItemEffect(cunningItemID, func(agent core.Agent, _ proto.ItemLevelState) { character := agent.GetCharacter() - numTargets := character.Env.GetNumTargets() - actionID := core.ActionID{SpellID: []int32{109798, 108005, 109800}[version]} minDmg := []float64{2498, 2820, 3183}[version] maxDmg := []float64{3747, 4230, 4774}[version] @@ -1021,10 +1018,11 @@ func init() { BonusCoefficient: spMod, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + numTargets := sim.Environment.ActiveTargetCount() results := make([]*core.SpellResult, numTargets) - for idx := int32(0); idx < numTargets; idx++ { - results[idx] = spell.CalcDamage(sim, sim.Environment.GetTargetUnit(idx), sim.Roll(minDmg, maxDmg), spell.OutcomeMagicCrit) + for idx, target := range sim.Environment.GetActiveTargets() { + results[idx] = spell.CalcDamage(sim, &target.Unit, sim.Roll(minDmg, maxDmg), spell.OutcomeMagicCrit) } spell.WaitTravelTime(sim, func(sim *core.Simulation) { @@ -1322,7 +1320,7 @@ func init() { AffectedByCastSpeed: false, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := dot.Spell.CalcAndDealPeriodicDamage(sim, aoeTarget, tickDamage, dot.Spell.OutcomeMagicCritNoHitCounter) if result.DidCrit() { diff --git a/sim/common/wotlk/shadowmourne.go b/sim/common/wotlk/shadowmourne.go index 6c26cdee76..74cc8fdf50 100644 --- a/sim/common/wotlk/shadowmourne.go +++ b/sim/common/wotlk/shadowmourne.go @@ -36,8 +36,8 @@ func init() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(1900, 2100) / float64(sim.GetNumTargets()) - for _, target := range sim.Encounter.TargetUnits { + baseDamage := sim.Roll(1900, 2100) / float64(sim.ActiveTargetCount()) + for _, target := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHit) // probably has a very low crit rate } }, diff --git a/sim/core/apl_actions_casting.go b/sim/core/apl_actions_casting.go index c147f0e97d..c9535fd8c2 100644 --- a/sim/core/apl_actions_casting.go +++ b/sim/core/apl_actions_casting.go @@ -142,13 +142,13 @@ func (rot *APLRotation) newActionMultidot(config *proto.APLActionMultidot) APLAc } maxDots := config.MaxDots - numTargets := unit.Env.GetNumTargets() + maxTargets := unit.Env.TotalTargetCount() if spell.Flags.Matches(SpellFlagHelpful) { - numTargets = int32(len(unit.Env.Raid.AllPlayerUnits)) + maxTargets = int32(len(unit.Env.Raid.AllPlayerUnits)) } - if numTargets < maxDots { - rot.ValidationMessage(proto.LogLevel_Warning, "Encounter only has %d targets. Using that for Max Dots instead of %d", numTargets, maxDots) - maxDots = numTargets + if maxTargets < maxDots { + rot.ValidationMessage(proto.LogLevel_Warning, "Encounter only has %d targets. Using that for Max Dots instead of %d", maxTargets, maxDots) + maxDots = maxTargets } return &APLActionMultidot{ @@ -177,7 +177,7 @@ func (action *APLActionMultidot) IsReady(sim *Simulation) bool { } } else { for i := int32(0); i < action.maxDots; i++ { - target := sim.Encounter.TargetUnits[i] + target := sim.Encounter.AllTargetUnits[i] dot := action.spell.Dot(target) if (!dot.IsActive() || dot.RemainingDuration(sim) < maxOverlap) && action.spell.CanCastOrQueue(sim, target) { action.nextTarget = target @@ -228,10 +228,8 @@ func (rot *APLRotation) newActionStrictMultidot(config *proto.APLActionStrictMul targets = unit.Env.Raid.AllPlayerUnits numTargets = int32(len(targets)) } else { - numTargets = unit.Env.GetNumTargets() - targets = MapSlice(unit.Env.ActiveTargetUnits(), func(target *Target) *Unit { - return &target.Unit - }) + numTargets = unit.Env.TotalTargetCount() + targets = unit.Env.Encounter.AllTargetUnits } if numTargets < maxDots { rot.ValidationMessage(proto.LogLevel_Warning, "Encounter only has %d targets. Using that for Max Dots instead of %d", numTargets, maxDots) diff --git a/sim/core/apl_values_encounter.go b/sim/core/apl_values_encounter.go index 6c772bf2d9..130883bf8e 100644 --- a/sim/core/apl_values_encounter.go +++ b/sim/core/apl_values_encounter.go @@ -86,10 +86,10 @@ func (value *APLValueNumberTargets) Type() proto.APLValueType { return proto.APLValueType_ValueTypeInt } func (value *APLValueNumberTargets) GetInt(sim *Simulation) int32 { - return sim.GetNumTargets() + return sim.ActiveTargetCount() } func (value *APLValueNumberTargets) String() string { - return "Num Targets" + return "Num Active Targets" } type APLValueIsExecutePhase struct { diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 02ec32cef7..3ae09494fc 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -1686,7 +1686,7 @@ func registerShatteringThrowCD(agent Agent, numShatteringThrows int32) { return } - stAura := ShatteringThrowAura(agent.GetCharacter().Env.Encounter.TargetUnits[0], -1) + stAura := ShatteringThrowAura(agent.GetCharacter().Env.GetTargetUnitByIndex(0), -1) registerExternalConsecutiveCDApproximation( agent, diff --git a/sim/core/cast.go b/sim/core/cast.go index e256916856..5de4ba5762 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -98,6 +98,10 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { } } + if (target.Type != PlayerUnit) && !target.IsEnabled() { + return spell.castFailureHelper(sim, "target disabled") + } + if spell.Flags.Matches(SpellFlagSwapped) { return spell.castFailureHelper(sim, "spell attached to an un-equipped item") } @@ -228,6 +232,10 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { func (spell *Spell) makeCastFuncSimple() CastSuccessFunc { return func(sim *Simulation, target *Unit) bool { + if (target.Type != PlayerUnit) && !target.IsEnabled() { + return spell.castFailureHelper(sim, "target disabled") + } + if spell.Flags.Matches(SpellFlagSwapped) { return spell.castFailureHelper(sim, "spell attached to an un-equipped item") } diff --git a/sim/core/consumes.go b/sim/core/consumes.go index 4d3019804b..a356972f8e 100644 --- a/sim/core/consumes.go +++ b/sim/core/consumes.go @@ -354,7 +354,7 @@ func registerExplosivesCD(agent Agent, consumes *proto.ConsumesSpec) { ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) { baseDamage := 5006 * sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/core/environment.go b/sim/core/environment.go index 628d6f7b44..ffba1be7dc 100644 --- a/sim/core/environment.go +++ b/sim/core/environment.go @@ -57,7 +57,7 @@ func NewEnvironment(raidProto *proto.Raid, encounterProto *proto.Encounter, runF env.finalize(raidProto, encounterProto, raidStats, runFakePrepull) encounterStats := &proto.EncounterStats{} - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { encounterStats.Targets = append(encounterStats.Targets, &proto.TargetStats{ Metadata: target.GetMetadata(), }) @@ -75,7 +75,7 @@ func (env *Environment) construct(raidProto *proto.Raid, encounterProto *proto.E env.Raid.updatePlayersAndPets() - env.AllUnits = append(env.Encounter.TargetUnits, env.Raid.AllUnits...) + env.AllUnits = append(env.Encounter.AllTargetUnits, env.Raid.AllUnits...) for unitIndex, unit := range env.AllUnits { unit.Env = env @@ -83,19 +83,19 @@ func (env *Environment) construct(raidProto *proto.Raid, encounterProto *proto.E } for _, unit := range env.Raid.AllUnits { - unit.CurrentTarget = env.Encounter.TargetUnits[0] + unit.CurrentTarget = env.Encounter.ActiveTargetUnits[0] } // Apply extra debuffs from raid. - if raidProto.Debuffs != nil && len(env.Encounter.TargetUnits) > 0 { - for targetIdx, targetUnit := range env.Encounter.TargetUnits { + if raidProto.Debuffs != nil && len(env.Encounter.AllTargetUnits) > 0 { + for targetIdx, targetUnit := range env.Encounter.AllTargetUnits { applyDebuffEffects(targetUnit, targetIdx, raidProto.Debuffs, raidProto) } } tankTargetSet := map[*Unit]bool{} // Assign target-of-target using Tanks field. - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { if target.Index < int32(len(encounterProto.Targets)) { targetProto := encounterProto.Targets[target.Index] env.setupTankTarget(target, targetProto.TankIndex, raidProto.Tanks, true, tankTargetSet) @@ -111,7 +111,7 @@ func (env *Environment) construct(raidProto *proto.Raid, encounterProto *proto.E // The initialization phase. func (env *Environment) initialize(raidProto *proto.Raid, encounterProto *proto.Encounter) *proto.RaidStats { - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { if target.Index < int32(len(encounterProto.Targets)) { target.initialize(encounterProto.Targets[target.Index]) } else { @@ -144,7 +144,7 @@ func (env *Environment) finalize(raidProto *proto.Raid, _ *proto.Encounter, raid } env.preFinalizeEffects = nil - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { target.finalize() if target.AI != nil { target.Rotation = target.newCustomRotation() @@ -263,7 +263,7 @@ func (env *Environment) reset(sim *Simulation) { // Targets need to be reset before the raid, so that players can check for // the presence of permanent target auras in their Reset handlers. - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { target.Reset(sim) } @@ -275,25 +275,28 @@ func (env *Environment) GetMaxDuration() time.Duration { return env.BaseDuration + env.DurationVariation } -func (env *Environment) GetNumTargets() int32 { +func (env *Environment) ActiveTargetCount() int32 { return int32(len(env.Encounter.ActiveTargets)) } +func (env *Environment) TotalTargetCount() int32 { + return int32(len(env.Encounter.AllTargets)) +} -func (env *Environment) ActiveTargetUnits() []*Target { +func (env *Environment) GetActiveTargets() []*Target { return env.Encounter.ActiveTargets } -func (env *Environment) GetTarget(index int32) *Target { - return env.Encounter.Targets[index] +func (env *Environment) GetTargetByIndex(index int32) *Target { + return env.Encounter.AllTargets[index] } -func (env *Environment) GetTargetUnit(index int32) *Unit { - return &env.Encounter.Targets[index].Unit +func (env *Environment) GetTargetUnitByIndex(index int32) *Unit { + return env.Encounter.AllTargetUnits[index] } -func (env *Environment) NextTarget(target *Unit) *Target { - return env.Encounter.Targets[target.Index].NextTarget() +func (env *Environment) NextActiveTarget(target *Unit) *Target { + return env.Encounter.AllTargets[target.Index].NextActiveTarget() } -func (env *Environment) NextTargetUnit(target *Unit) *Unit { - return &env.NextTarget(target).Unit +func (env *Environment) NextActiveTargetUnit(target *Unit) *Unit { + return &env.NextActiveTarget(target).Unit } func (env *Environment) GetAgentFromUnit(unit *Unit) Agent { raidAgent := env.Raid.GetPlayerFromUnit(unit) @@ -301,7 +304,7 @@ func (env *Environment) GetAgentFromUnit(unit *Unit) Agent { return raidAgent } - for _, target := range env.Encounter.Targets { + for _, target := range env.Encounter.AllTargets { if unit == &target.Unit { return target } @@ -341,8 +344,8 @@ func (env *Environment) GetUnit(ref *proto.UnitReference, contextUnit *Unit) *Un return nil } case proto.UnitReference_Target: - if int(ref.Index) < len(env.Encounter.TargetUnits) { - return env.Encounter.TargetUnits[ref.Index] + if int(ref.Index) < len(env.Encounter.AllTargetUnits) { + return env.Encounter.AllTargetUnits[ref.Index] } else { return nil } diff --git a/sim/core/health.go b/sim/core/health.go index 2b1c91f276..babd3b2fac 100644 --- a/sim/core/health.go +++ b/sim/core/health.go @@ -102,7 +102,7 @@ var ChanceOfDeathAuraLabel = "Chance of Death" func (character *Character) trackChanceOfDeath(healingModel *proto.HealingModel) { character.Unit.Metrics.isTanking = false - for _, target := range character.Env.Encounter.TargetUnits { + for _, target := range character.Env.Encounter.AllTargetUnits { if (target.CurrentTarget == &character.Unit) || (target.SecondaryTarget == &character.Unit) { character.Unit.Metrics.isTanking = true } @@ -194,7 +194,7 @@ func (character *Character) applyHealingModel(healingModel *proto.HealingModel) if absorbFrac > 0 { absorbShield = character.NewDamageAbsorptionAura("Healing Model Absorb Shield", healingModelActionID, NeverExpires, func(_ *Unit) float64 { - return max(absorbShield.ShieldStrength, healPerTick * absorbFrac) + return max(absorbShield.ShieldStrength, healPerTick*absorbFrac) }) } @@ -216,7 +216,7 @@ func (character *Character) applyHealingModel(healingModel *proto.HealingModel) if healPerTick > 0 { // Execute the direct portion of the heal - character.GainHealth(sim, healPerTick * (1.0 - absorbFrac), healthMetrics) + character.GainHealth(sim, healPerTick*(1.0-absorbFrac), healthMetrics) // Turn the remainder into an absorb shield if absorbShield != nil { diff --git a/sim/core/sim.go b/sim/core/sim.go index 9b82e3338b..fd12a8a5e8 100644 --- a/sim/core/sim.go +++ b/sim/core/sim.go @@ -471,7 +471,7 @@ func (sim *Simulation) Cleanup() { for _, unit := range sim.Raid.AllUnits { unit.Metrics.doneIteration(unit, sim) } - for _, target := range sim.Encounter.TargetUnits { + for _, target := range sim.Encounter.AllTargetUnits { target.Metrics.doneIteration(target, sim) } } diff --git a/sim/core/spell.go b/sim/core/spell.go index dbd3d42929..6834096735 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -414,7 +414,7 @@ func (spell *Spell) CurDamagePerCast() float64 { } else { casts := int32(0) damage := 0.0 - for _, opponent := range spell.Unit.GetOpponents() { + for _, opponent := range spell.Unit.GetAllOpponents() { casts += spell.SpellMetrics[opponent.UnitIndex].Casts damage += spell.SpellMetrics[opponent.UnitIndex].TotalDamage } @@ -511,6 +511,10 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { return false } + if (target.Type != PlayerUnit) && !target.IsEnabled() { + return false + } + if spell.Flags.Matches(SpellFlagSwapped) { //if sim.Log != nil { // sim.Log("Cant cast because of item swap") @@ -602,9 +606,8 @@ func (spell *Spell) applyEffects(sim *Simulation, target *Unit) { } func (spell *Spell) ApplyAOEThreatIgnoreMultipliers(threatAmount float64) { - numTargets := spell.Unit.Env.GetNumTargets() - for i := int32(0); i < numTargets; i++ { - spell.SpellMetrics[i].TotalThreat += threatAmount + for _, target := range spell.Unit.Env.GetActiveTargets() { + spell.SpellMetrics[target.UnitIndex].TotalThreat += threatAmount } } func (spell *Spell) ApplyAOEThreat(threatAmount float64) { diff --git a/sim/core/spell_queueing.go b/sim/core/spell_queueing.go index e401f292e5..f0d799f536 100644 --- a/sim/core/spell_queueing.go +++ b/sim/core/spell_queueing.go @@ -80,6 +80,17 @@ func (spell *Spell) CanQueue(sim *Simulation, target *Unit) bool { return false } + if (target.Type != PlayerUnit) && !target.IsEnabled() { + return false + } + + if spell.Flags.Matches(SpellFlagSwapped) { + //if sim.Log != nil { + // sim.Log("Cant cast because of item swap") + //} + return false + } + // Same extra cast conditions apply as if we were casting right now if spell.ExtraCastCondition != nil && !spell.ExtraCastCondition(sim, target) { return false diff --git a/sim/core/target.go b/sim/core/target.go index bab3407269..8a5c0e3e49 100644 --- a/sim/core/target.go +++ b/sim/core/target.go @@ -1,6 +1,7 @@ package core import ( + "slices" "strconv" "time" @@ -11,9 +12,10 @@ import ( type Encounter struct { Duration time.Duration DurationVariation time.Duration - Targets []*Target + AllTargets []*Target ActiveTargets []*Target - TargetUnits []*Unit + AllTargetUnits []*Unit + ActiveTargetUnits []*Unit ExecuteProportion_20 float64 ExecuteProportion_25 float64 @@ -34,6 +36,7 @@ type Encounter struct { func NewEncounter(options *proto.Encounter) Encounter { options.ExecuteProportion_25 = max(options.ExecuteProportion_25, options.ExecuteProportion_20) options.ExecuteProportion_35 = max(options.ExecuteProportion_35, options.ExecuteProportion_25) + totalTargetCount := max(len(options.Targets), 1) encounter := Encounter{ Duration: DurationFromSeconds(options.Duration), @@ -42,22 +45,35 @@ func NewEncounter(options *proto.Encounter) Encounter { ExecuteProportion_25: max(options.ExecuteProportion_25, 0), ExecuteProportion_35: max(options.ExecuteProportion_35, 0), ExecuteProportion_90: max(options.ExecuteProportion_90, 0), - Targets: []*Target{}, - ActiveTargets: []*Target{}, + AllTargets: make([]*Target, 0, totalTargetCount), + ActiveTargets: make([]*Target, 0, totalTargetCount), + AllTargetUnits: make([]*Unit, 0, totalTargetCount), + ActiveTargetUnits: make([]*Unit, 0, totalTargetCount), } + for targetIndex, targetOptions := range options.Targets { target := NewTarget(targetOptions, int32(targetIndex)) - encounter.Targets = append(encounter.Targets, target) - encounter.ActiveTargets = append(encounter.ActiveTargets, target) - encounter.TargetUnits = append(encounter.TargetUnits, &target.Unit) + encounter.AllTargets = append(encounter.AllTargets, target) + encounter.AllTargetUnits = append(encounter.AllTargetUnits, &target.Unit) + + if target.IsEnabled() { + encounter.ActiveTargets = append(encounter.ActiveTargets, target) + encounter.ActiveTargetUnits = append(encounter.ActiveTargetUnits, &target.Unit) + } } - if len(encounter.Targets) == 0 { + + if len(encounter.AllTargets) == 0 { // Add a dummy target. The only case where targets aren't specified is when // computing character stats, and targets won't matter there. target := NewTarget(&proto.Target{}, 0) - encounter.Targets = append(encounter.Targets, target) + encounter.AllTargets = append(encounter.AllTargets, target) encounter.ActiveTargets = append(encounter.ActiveTargets, target) - encounter.TargetUnits = append(encounter.TargetUnits, &target.Unit) + encounter.AllTargetUnits = append(encounter.AllTargetUnits, &target.Unit) + encounter.ActiveTargetUnits = append(encounter.ActiveTargetUnits, &target.Unit) + } + + if len(encounter.ActiveTargets) == 0 { + panic("At least one target must be active at the start of the simulation!") } // If UseHealth is set, we use the sum of targets health. After creating the targets to make sure stat modifications are done @@ -85,25 +101,51 @@ func (encounter *Encounter) AOECapMultiplier() float64 { return encounter.aoeCapMultiplier } func (encounter *Encounter) updateAOECapMultiplier() { - encounter.aoeCapMultiplier = min(10/float64(len(encounter.Targets)), 1) + encounter.aoeCapMultiplier = min(10/float64(len(encounter.ActiveTargets)), 1) +} + +func (encounter *Encounter) addActiveTarget(target *Target) { + if !slices.Contains(encounter.AllTargets, target) { + panic("Target was not defined during the construction phase of the encounter!") + } + + if slices.Contains(encounter.ActiveTargets, target) { + panic("Target is already present in active target list!") + } + + encounter.ActiveTargets = append(encounter.ActiveTargets, target) + encounter.ActiveTargetUnits = append(encounter.ActiveTargetUnits, &target.Unit) + encounter.updateAOECapMultiplier() +} + +func (encounter *Encounter) removeInactiveTarget(target *Target) { + if len(encounter.ActiveTargets) == 1 { + panic("Cannot remove the only active target in the simulation!") + } + + if idx := slices.Index(encounter.ActiveTargets, target); idx != -1 { + encounter.ActiveTargets = removeBySwappingToBack(encounter.ActiveTargets, idx) + encounter.ActiveTargetUnits = removeBySwappingToBack(encounter.ActiveTargetUnits, idx) + } else { + panic("Target is not present in active target list!") + } + + encounter.updateAOECapMultiplier() } func (encounter *Encounter) doneIteration(sim *Simulation) { - for i := range encounter.Targets { - target := encounter.Targets[i] + for _, target := range encounter.AllTargets { target.doneIteration(sim) } } func (encounter *Encounter) GetMetricsProto() *proto.EncounterMetrics { metrics := &proto.EncounterMetrics{ - Targets: make([]*proto.UnitMetrics, len(encounter.Targets)), + Targets: make([]*proto.UnitMetrics, len(encounter.AllTargets)), } - i := 0 - for _, target := range encounter.Targets { - metrics.Targets[i] = target.GetMetricsProto() - i++ + for idx, target := range encounter.AllTargets { + metrics.Targets[idx] = target.GetMetricsProto() } return metrics @@ -113,8 +155,6 @@ func (encounter *Encounter) GetMetricsProto() *proto.EncounterMetrics { type Target struct { Unit - IsActive bool - AI TargetAI } @@ -138,8 +178,8 @@ func NewTarget(options *proto.Target, targetIndex int32) *Target { StatDependencyManager: stats.NewStatDependencyManager(), ReactionTime: time.Millisecond * 1620, + enabled: !options.DisabledAtStart, }, - IsActive: true, } defaultRaidBossLevel := int32(CharacterLevel + 3) target.GCD = target.NewTimer() @@ -174,18 +214,83 @@ func (target *Target) Reset(sim *Simulation) { target.Unit.reset(sim, nil) target.CurrentTarget = target.defaultTarget + if !target.IsEnabled() && (target.CurrentTarget != nil) { + target.CurrentTarget.CurrentTarget = &target.NextActiveTarget().Unit + target.CurrentTarget = nil + } + target.SetGCDTimer(sim, 0) + if target.AI != nil { target.AI.Reset(sim) } } -func (target *Target) NextTarget() *Target { +func (target *Target) Enable(sim *Simulation) { + if target.defaultTarget != nil { + target.defaultTarget.CurrentTarget = &target.Unit + target.CurrentTarget = target.defaultTarget + } + + target.AutoAttacks.EnableAutoSwing(sim) + + // Randomize GCD and swing timings to prevent fake APL-Haste couplings. + target.ExtendGCDUntil(sim, sim.CurrentTime+DurationFromSeconds(sim.RandomFloat("Specials Timing")*BossGCD.Seconds())) + target.AutoAttacks.RandomizeMeleeTiming(sim) + + if !target.IsEnabled() { + target.enabled = true + sim.Encounter.addActiveTarget(target) + } +} + +func (sim *Simulation) EnableTargetUnit(targetUnit *Unit) { + if targetUnit.Type != EnemyUnit { + panic("Unit is not an enemy target!") + } + + sim.Encounter.AllTargets[targetUnit.Index].Enable(sim) +} + +func (target *Target) Disable(sim *Simulation) { + if !target.IsEnabled() { + return + } + + target.CancelGCDTimer(sim) + target.AutoAttacks.CancelAutoSwing(sim) + target.enabled = false + sim.Encounter.removeInactiveTarget(target) + target.auraTracker.expireAll(sim) + + if target.CurrentTarget != nil { + target.CurrentTarget.CurrentTarget = &target.NextActiveTarget().Unit + target.CurrentTarget = nil + } +} + +func (sim *Simulation) DisableTargetUnit(targetUnit *Unit) { + if targetUnit.Type != EnemyUnit { + panic("Unit is not an enemy target!") + } + + sim.Encounter.AllTargets[targetUnit.Index].Disable(sim) +} + +func (target *Target) NextActiveTarget() *Target { nextIndex := target.Index + 1 - if nextIndex >= target.Env.GetNumTargets() { + + if nextIndex >= target.Env.TotalTargetCount() { nextIndex = 0 } - return target.Env.GetTarget(nextIndex) + + nextTarget := target.Env.GetTargetByIndex(nextIndex) + + if nextTarget.IsEnabled() { + return nextTarget + } else { + return nextTarget.NextActiveTarget() + } } func (target *Target) GetMetricsProto() *proto.UnitMetrics { diff --git a/sim/core/target_ai.go b/sim/core/target_ai.go index b38d3ef73b..e14e1c11bb 100644 --- a/sim/core/target_ai.go +++ b/sim/core/target_ai.go @@ -56,7 +56,7 @@ func (target *Target) GetCharacter() *Character { return nil } func (target *Target) Initialize() {} func (target *Target) ExecuteCustomRotation(sim *Simulation) { - if target.AI != nil { + if (target.AI != nil) && target.IsEnabled() { target.AI.ExecuteCustomRotation(sim) } } diff --git a/sim/core/test_utils.go b/sim/core/test_utils.go index 6995075f93..773cd73a8e 100644 --- a/sim/core/test_utils.go +++ b/sim/core/test_utils.go @@ -31,20 +31,24 @@ var AverageDefaultSimTestOptions = &proto.SimOptions{ const ShortDuration = 60 const LongDuration = 300 -var DefaultTargetProto = &proto.Target{ - Level: CharacterLevel + 3, - Stats: stats.Stats{ - stats.Armor: 11977, - stats.AttackPower: 650, - }.ToProtoArray(), - MobType: proto.MobType_MobTypeMechanical, - - SwingSpeed: 2.5, - MinBaseDamage: 210000, - ParryHaste: false, - DamageSpread: 0.4, +func FreshDefaultTargetConfig() *proto.Target { + return &proto.Target{ + Level: CharacterLevel + 3, + Stats: stats.Stats{ + stats.Armor: 11977, + stats.AttackPower: 650, + }.ToProtoArray(), + MobType: proto.MobType_MobTypeMechanical, + + SwingSpeed: 2.5, + MinBaseDamage: 210000, + ParryHaste: false, + DamageSpread: 0.4, + } } +var DefaultTargetProto = FreshDefaultTargetConfig() + var FullRaidBuffs = &proto.RaidBuffs{ BlessingOfKings: true, PowerWordFortitude: true, @@ -95,9 +99,15 @@ func NewDefaultTarget() *proto.Target { func MakeDefaultEncounterCombos() []EncounterCombo { var DefaultTarget = NewDefaultTarget() - multipleTargets := make([]*proto.Target, 20) + multipleTargets := make([]*proto.Target, 21) for i := range multipleTargets { - multipleTargets[i] = DefaultTarget + if i < 20 { + multipleTargets[i] = DefaultTarget + } else { + disabledTarget := FreshDefaultTargetConfig() + disabledTarget.DisabledAtStart = true + multipleTargets[i] = disabledTarget + } } return []EncounterCombo{ diff --git a/sim/core/unit.go b/sim/core/unit.go index 27423f35f6..0cabbd4857 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -211,11 +211,11 @@ func (unit *Unit) IsOpponent(other *Unit) bool { return (unit.Type == EnemyUnit) != (other.Type == EnemyUnit) } -func (unit *Unit) GetOpponents() []*Unit { +func (unit *Unit) GetAllOpponents() []*Unit { if unit.Type == EnemyUnit { return unit.Env.Raid.AllUnits } else { - return unit.Env.Encounter.TargetUnits + return unit.Env.Encounter.AllTargetUnits } } @@ -563,7 +563,10 @@ func (unit *Unit) finalize() { } func (unit *Unit) reset(sim *Simulation, _ Agent) { - unit.enabled = true + if unit.Type != EnemyUnit { + unit.enabled = true + } + unit.resetCDs(sim) unit.Hardcast.Expires = startingCDTime unit.ChanneledDot = nil diff --git a/sim/death_knight/blood/heart_strike.go b/sim/death_knight/blood/heart_strike.go index 7d0492f234..a99bd3fe5a 100644 --- a/sim/death_knight/blood/heart_strike.go +++ b/sim/death_knight/blood/heart_strike.go @@ -8,8 +8,8 @@ import ( var HeartStrikeActionID = core.ActionID{SpellID: 55050} func (dk *BloodDeathKnight) registerHeartStrikeSpell() { - numHits := min(3, dk.Env.GetNumTargets()) - results := make([]*core.SpellResult, numHits) + maxHits := min(3, dk.Env.TotalTargetCount()) + results := make([]*core.SpellResult, maxHits) dk.GetOrRegisterSpell(core.SpellConfig{ ActionID: HeartStrikeActionID, @@ -38,8 +38,9 @@ func (dk *BloodDeathKnight) registerHeartStrikeSpell() { baseDamage := dk.ClassSpellScaling*0.72799998522 + spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) + numHits := min(maxHits, sim.Environment.ActiveTargetCount()) currentTarget := target - for idx := int32(0); idx < numHits; idx++ { + for idx := range numHits { targetDamage := baseDamage * dk.GetDiseaseMulti(currentTarget, 1.0, 0.15) results[idx] = spell.CalcDamage(sim, currentTarget, targetDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) @@ -48,11 +49,11 @@ func (dk *BloodDeathKnight) registerHeartStrikeSpell() { } spell.DamageMultiplier *= 0.75 - currentTarget = dk.Env.NextTargetUnit(currentTarget) + currentTarget = dk.Env.NextActiveTargetUnit(currentTarget) } - for _, result := range results { - spell.DealDamage(sim, result) + for idx := range numHits { + spell.DealDamage(sim, results[idx]) spell.DamageMultiplier /= 0.75 } }, @@ -60,8 +61,8 @@ func (dk *BloodDeathKnight) registerHeartStrikeSpell() { } func (dk *BloodDeathKnight) registerDrwHeartStrikeSpell() *core.Spell { - numHits := min(3, dk.Env.GetNumTargets()) - results := make([]*core.SpellResult, numHits) + maxHits := min(3, dk.Env.TotalTargetCount()) + results := make([]*core.SpellResult, maxHits) return dk.RuneWeapon.RegisterSpell(core.SpellConfig{ ActionID: HeartStrikeActionID, SpellSchool: core.SpellSchoolPhysical, @@ -72,6 +73,7 @@ func (dk *BloodDeathKnight) registerDrwHeartStrikeSpell() *core.Spell { baseDamage := dk.ClassSpellScaling*0.72799998522 + spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) + numHits := min(maxHits, dk.Env.ActiveTargetCount()) currentTarget := target for idx := int32(0); idx < numHits; idx++ { targetDamage := baseDamage * dk.RuneWeapon.GetDiseaseMulti(currentTarget, 1.0, 0.15) @@ -79,11 +81,11 @@ func (dk *BloodDeathKnight) registerDrwHeartStrikeSpell() *core.Spell { results[idx] = spell.CalcDamage(sim, currentTarget, targetDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) spell.DamageMultiplier *= 0.75 - currentTarget = dk.Env.NextTargetUnit(currentTarget) + currentTarget = dk.Env.NextActiveTargetUnit(currentTarget) } - for _, result := range results { - spell.DealDamage(sim, result) + for idx := range numHits { + spell.DealDamage(sim, results[idx]) spell.DamageMultiplier /= 0.75 } }, diff --git a/sim/death_knight/blood_boil.go b/sim/death_knight/blood_boil.go index cadfce2a51..1eac18459b 100644 --- a/sim/death_knight/blood_boil.go +++ b/sim/death_knight/blood_boil.go @@ -8,7 +8,7 @@ var BloodBoilActionID = core.ActionID{SpellID: 48721} func (dk *DeathKnight) registerBloodBoilSpell() { rpMetric := dk.NewRunicPowerMetrics(BloodBoilActionID) - results := make([]*core.SpellResult, dk.Env.GetNumTargets()) + results := make([]*core.SpellResult, dk.Env.TotalTargetCount()) dk.RegisterSpell(core.SpellConfig{ ActionID: BloodBoilActionID, Flags: core.SpellFlagAPL, @@ -31,44 +31,44 @@ func (dk *DeathKnight) registerBloodBoilSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { anyHit := false - for idx, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := dk.ClassSpellScaling*0.31700000167 + 0.08*spell.MeleeAttackPower() baseDamage *= core.TernaryFloat64(dk.DiseasesAreActive(aoeTarget), 1.5, 1.0) baseDamage *= sim.Encounter.AOECapMultiplier() - results[idx] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) - anyHit = anyHit || results[idx].Landed() + results[aoeTarget.Index] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + anyHit = anyHit || results[aoeTarget.Index].Landed() } if anyHit { dk.AddRunicPower(sim, 10, rpMetric) } - for _, result := range results { - spell.DealDamage(sim, result) + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { + spell.DealDamage(sim, results[aoeTarget.Index]) } }, }) } func (dk *DeathKnight) registerDrwBloodBoilSpell() *core.Spell { - results := make([]*core.SpellResult, dk.Env.GetNumTargets()) + results := make([]*core.SpellResult, dk.Env.TotalTargetCount()) return dk.RuneWeapon.RegisterSpell(core.SpellConfig{ ActionID: BloodBoilActionID, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for idx, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := dk.ClassSpellScaling*0.31700000167 + 0.08*spell.MeleeAttackPower() baseDamage *= core.TernaryFloat64(dk.RuneWeapon.DiseasesAreActive(aoeTarget), 1.5, 1.0) baseDamage *= sim.Encounter.AOECapMultiplier() - results[idx] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + results[aoeTarget.Index] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } - for _, result := range results { - spell.DealDamage(sim, result) + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { + spell.DealDamage(sim, results[aoeTarget.Index]) } }, }) diff --git a/sim/death_knight/death_and_decay.go b/sim/death_knight/death_and_decay.go index 1abb32cd2c..bd5a6b6628 100644 --- a/sim/death_knight/death_and_decay.go +++ b/sim/death_knight/death_and_decay.go @@ -43,7 +43,7 @@ func (dk *DeathKnight) registerDeathAndDecaySpell() { OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { // DnD recalculates everything on each tick baseDamage := 26 + dot.Spell.MeleeAttackPower()*0.06400000304 - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.Spell.SpellMetrics[aoeTarget.UnitIndex].Casts++ dot.Spell.CalcAndDealPeriodicDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) } diff --git a/sim/death_knight/ghoul_pet.go b/sim/death_knight/ghoul_pet.go index b29fcd65b1..750ea006b7 100644 --- a/sim/death_knight/ghoul_pet.go +++ b/sim/death_knight/ghoul_pet.go @@ -183,12 +183,12 @@ func (ghoulPet *GhoulPet) registerClaw() *core.Spell { BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - results := make([]*core.SpellResult, min(int32(2), min(ghoulPet.Env.GetNumTargets(), core.TernaryInt32(ghoulPet.DarkTransformationAura.IsActive(), 2, 1)))) + results := make([]*core.SpellResult, min(int32(2), min(ghoulPet.Env.ActiveTargetCount(), core.TernaryInt32(ghoulPet.DarkTransformationAura.IsActive(), 2, 1)))) for idx := range results { baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) results[idx] = spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - target = sim.Environment.NextTargetUnit(target) + target = sim.Environment.NextActiveTargetUnit(target) } for idx, result := range results { diff --git a/sim/death_knight/howling_blast.go b/sim/death_knight/howling_blast.go index 4c569bd53c..f27033ee0f 100644 --- a/sim/death_knight/howling_blast.go +++ b/sim/death_knight/howling_blast.go @@ -11,7 +11,7 @@ func (dk *DeathKnight) registerHowlingBlastSpell() { return } - results := make([]*core.SpellResult, dk.Env.GetNumTargets()) + results := make([]*core.SpellResult, dk.Env.TotalTargetCount()) dk.RegisterSpell(core.SpellConfig{ ActionID: HowlingBlastActionID, @@ -37,24 +37,24 @@ func (dk *DeathKnight) registerHowlingBlastSpell() { CritMultiplier: dk.DefaultMeleeCritMultiplier(), ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for idx, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := dk.ClassSpellScaling*1.17499995232 + 0.44*spell.MeleeAttackPower() if aoeTarget != target { spell.DamageMultiplier *= 0.5 - results[idx] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + results[aoeTarget.Index] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) spell.DamageMultiplier /= 0.5 } else { - results[idx] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + results[aoeTarget.Index] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } if aoeTarget == target { - spell.SpendRefundableCost(sim, results[idx]) + spell.SpendRefundableCost(sim, results[aoeTarget.Index]) } } - for _, result := range results { - spell.DealDamage(sim, result) + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { + spell.DealDamage(sim, results[aoeTarget.Index]) } }, }) diff --git a/sim/death_knight/pestilence.go b/sim/death_knight/pestilence.go index 276f1b146f..2567ed10e7 100644 --- a/sim/death_knight/pestilence.go +++ b/sim/death_knight/pestilence.go @@ -41,7 +41,7 @@ func (dk *DeathKnight) registerPestilenceSpell() { frostFeverActive := dk.FrostFeverSpell.Dot(target).IsActive() bloodPlagueActive := dk.BloodPlagueSpell.Dot(target).IsActive() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := spell.CalcAndDealOutcome(sim, aoeTarget, spell.OutcomeMagicHit) if aoeTarget == target { diff --git a/sim/death_knight/talents_frost.go b/sim/death_knight/talents_frost.go index c5c15ecf79..897340e64e 100644 --- a/sim/death_knight/talents_frost.go +++ b/sim/death_knight/talents_frost.go @@ -111,7 +111,7 @@ func (dk *DeathKnight) applyMercilessCombat() { dk.RegisterResetEffect(func(sim *core.Simulation) { sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { if isExecute == 35 { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.AllTargetUnits { debuffs.Get(aoeTarget).Activate(sim) } } diff --git a/sim/druid/demoralizing_roar.go b/sim/druid/demoralizing_roar.go index fa64204cc4..ddeeac96c6 100644 --- a/sim/druid/demoralizing_roar.go +++ b/sim/druid/demoralizing_roar.go @@ -29,7 +29,7 @@ func (druid *Druid) registerDemoralizingRoarSpell() { FlatThreatBonus: 62 * 2, // TODO: Measure for Cata ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := spell.CalcAndDealOutcome(sim, aoeTarget, spell.OutcomeMagicHit) if result.Landed() { druid.DemoralizingRoarAuras.Get(aoeTarget).Activate(sim) diff --git a/sim/druid/feral/TestFeral.results b/sim/druid/feral/TestFeral.results index 2fe1e9a3bb..0d318999fb 100644 --- a/sim/druid/feral/TestFeral.results +++ b/sim/druid/feral/TestFeral.results @@ -1688,7 +1688,7 @@ dps_results: { value: { dps: 33425.34705 tps: 45378.09998 - hps: 413.8538 + hps: 103.70309 } } dps_results: { diff --git a/sim/druid/feral/apl_values.go b/sim/druid/feral/apl_values.go index d40f0ce093..d87c97129d 100644 --- a/sim/druid/feral/apl_values.go +++ b/sim/druid/feral/apl_values.go @@ -161,7 +161,7 @@ func (action *APLActionCatOptimalRotationAction) Execute(sim *core.Simulation) { // Keep up Sunder debuff if not provided externally. Do this here since FF can be // cast while moving. if cat.Rotation.MaintainFaerieFire { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { if cat.ShouldFaerieFire(sim, aoeTarget) { cat.FaerieFire.CastOrQueue(sim, aoeTarget) } diff --git a/sim/druid/feral/rotation_aoe.go b/sim/druid/feral/rotation_aoe.go index 1296670c4a..2b052e7319 100644 --- a/sim/druid/feral/rotation_aoe.go +++ b/sim/druid/feral/rotation_aoe.go @@ -8,7 +8,7 @@ import ( func (cat *FeralDruid) calcExpectedSwipeDamage(sim *core.Simulation) (float64, float64) { expectedSwipeDamage := 0.0 - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { expectedSwipeDamage += cat.SwipeCat.ExpectedInitialDamage(sim, aoeTarget) } swipeDPE := expectedSwipeDamage / cat.SwipeCat.DefaultCast.Cost @@ -76,7 +76,7 @@ func (cat *FeralDruid) doAoeRotation(sim *core.Simulation) (bool, time.Duration) rakeTarget := cat.CurrentTarget rakeDot := cat.Rake.CurDot() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { rakeDot = cat.Rake.Dot(aoeTarget) canRakeTarget := !rakeDot.IsActive() || ((rakeDot.RemainingDuration(sim) < rakeDot.BaseTickLength) && (!isClearcast || (rakeDot.RemainingDuration(sim) < time.Second))) @@ -106,7 +106,7 @@ func (cat *FeralDruid) doAoeRotation(sim *core.Simulation) (bool, time.Duration) mangleTarget := cat.CurrentTarget bleedAura := cat.bleedAura - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { rakeDot = cat.Rake.Dot(aoeTarget) bleedAura = aoeTarget.GetExclusiveEffectCategory(core.BleedEffectCategory).GetActiveAura() canMangleTarget := rakeDot.IsActive() && !bleedAura.IsActive() @@ -177,7 +177,7 @@ func (cat *FeralDruid) doAoeRotation(sim *core.Simulation) (bool, time.Duration) nextAction = min(nextAction, cat.SavageRoarAura.ExpiresAt()) } - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { rakeDot = cat.Rake.Dot(aoeTarget) rakeRefreshPending := rakeDot.IsActive() && (rakeDot.RemainingDuration(sim) < simTimeRemain-rakeDot.BaseTickLength) diff --git a/sim/druid/hurricane.go b/sim/druid/hurricane.go index 84f57174bb..f57338ace6 100644 --- a/sim/druid/hurricane.go +++ b/sim/druid/hurricane.go @@ -23,7 +23,7 @@ func (druid *Druid) registerHurricaneSpell() { damage := 0.327 * druid.ClassSpellScaling damage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/druid/items.go b/sim/druid/items.go index a148f5ecc0..874b66d98c 100644 --- a/sim/druid/items.go +++ b/sim/druid/items.go @@ -290,7 +290,7 @@ var ItemSetDeepEarthRegalia = core.NewItemSet(core.ItemSet{ return } - for _, target := range druid.Env.Encounter.TargetUnits { + for _, target := range druid.Env.Encounter.AllTargetUnits { spell.Dot(target).ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { if setBonusAura.IsActive() { t13InsectSwarmBonusDummyAuras.Get(aura.Unit).Activate(sim) diff --git a/sim/druid/mangle.go b/sim/druid/mangle.go index fc4652f46d..77abcef6e3 100644 --- a/sim/druid/mangle.go +++ b/sim/druid/mangle.go @@ -10,7 +10,6 @@ import ( func (druid *Druid) registerMangleBearSpell() { mangleAuras := druid.NewEnemyAuraArray(core.MangleAura) glyphBonus := core.TernaryFloat64(druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfMangle), 1.1, 1.0) - maxHits := min(druid.Env.GetNumTargets(), 3) druid.MangleBear = druid.RegisterSpell(Bear, core.SpellConfig{ ActionID: core.ActionID{SpellID: 33878}, @@ -41,6 +40,7 @@ func (druid *Druid) registerMangleBearSpell() { MaxRange: core.MaxMeleeRange, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + maxHits := min(druid.Env.ActiveTargetCount(), 3) numHits := core.TernaryInt32(druid.BerserkAura.IsActive(), maxHits, 1) curTarget := target @@ -54,7 +54,7 @@ func (druid *Druid) registerMangleBearSpell() { spell.IssueRefund(sim) } - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } if druid.BerserkAura.IsActive() { diff --git a/sim/druid/maul.go b/sim/druid/maul.go index f4d7162c5f..8df1b66c40 100644 --- a/sim/druid/maul.go +++ b/sim/druid/maul.go @@ -9,7 +9,7 @@ import ( func (druid *Druid) registerMaulSpell() { flatBaseDamage := 34.0 - numHits := core.TernaryInt32(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfMaul) && druid.Env.GetNumTargets() > 1, 2, 1) + maxHits := core.TernaryInt32(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfMaul), 2, 1) rendAndTearMod := []float64{1.0, 1.07, 1.13, 1.2}[druid.Talents.RendAndTear] druid.Maul = druid.RegisterSpell(Bear, core.SpellConfig{ @@ -39,6 +39,7 @@ func (druid *Druid) registerMaulSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := flatBaseDamage + 0.19*spell.MeleeAttackPower() + numHits := min(maxHits, druid.Env.ActiveTargetCount()) curTarget := target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { @@ -59,7 +60,7 @@ func (druid *Druid) registerMaulSpell() { spell.IssueRefund(sim) } - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } }, }) diff --git a/sim/druid/starfall.go b/sim/druid/starfall.go index 8e45aeb7c9..7831b0dd9f 100644 --- a/sim/druid/starfall.go +++ b/sim/druid/starfall.go @@ -12,7 +12,7 @@ func (druid *Druid) registerStarfallSpell() { return } - numberOfTicks := core.TernaryInt32(druid.Env.GetNumTargets() > 1, 20, 10) + numberOfTicks := core.TernaryInt32(druid.Env.TotalTargetCount() > 1, 20, 10) tickLength := time.Second starfallTickSpell := druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ diff --git a/sim/druid/swipe.go b/sim/druid/swipe.go index e14479bc76..8490409956 100644 --- a/sim/druid/swipe.go +++ b/sim/druid/swipe.go @@ -37,7 +37,7 @@ func (druid *Druid) registerSwipeBearSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := flatBaseDamage + 0.123*spell.MeleeAttackPower() baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) } }, @@ -71,7 +71,7 @@ func (druid *Druid) registerSwipeCatSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) } }, diff --git a/sim/druid/thrash.go b/sim/druid/thrash.go index 71aada6573..b9532ec6af 100644 --- a/sim/druid/thrash.go +++ b/sim/druid/thrash.go @@ -62,7 +62,7 @@ func (druid *Druid) registerThrashBearSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := flatBaseDamage + 0.0982*spell.MeleeAttackPower() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { perTargetDamage := (baseDamage + (sim.RandomFloat("Thrash") * damageSpread)) * sim.Encounter.AOECapMultiplier() if druid.BleedCategories.Get(aoeTarget).AnyActive() { perTargetDamage *= 1.3 diff --git a/sim/druid/typhoon.go b/sim/druid/typhoon.go index 72f17aa89b..cb268aa32f 100644 --- a/sim/druid/typhoon.go +++ b/sim/druid/typhoon.go @@ -45,7 +45,7 @@ func (druid *Druid) registerTyphoonSpell() { spell.WaitTravelTime(sim, func(sim *core.Simulation) { baseDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassDruid, 1.316) baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }) diff --git a/sim/druid/wild_mushrooms.go b/sim/druid/wild_mushrooms.go index 1c3324c06c..312f023a90 100644 --- a/sim/druid/wild_mushrooms.go +++ b/sim/druid/wild_mushrooms.go @@ -50,7 +50,7 @@ func (druid *Druid) registerWildMushrooms() { baseDamage := sim.Roll(min, max) baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/encounters/bwd/magmaw_ai.go b/sim/encounters/bwd/magmaw_ai.go index 1543760d61..a1eccd1cde 100644 --- a/sim/encounters/bwd/magmaw_ai.go +++ b/sim/encounters/bwd/magmaw_ai.go @@ -271,8 +271,8 @@ func (ai *MagmawAI) registerSpells() { } // TODO: Move this to an APL Action - if !isIndividualSim && ai.Target.Env.GetNumTargets() > 1 { - addTarget := ai.Target.Env.NextTargetUnit(&ai.Target.Unit) + if !isIndividualSim && ai.Target.Env.ActiveTargetCount() > 1 { + addTarget := ai.Target.Env.NextActiveTargetUnit(&ai.Target.Unit) if addTarget.CurrentTarget != nil { // Swap Tanks addTank := addTarget.CurrentTarget @@ -293,8 +293,8 @@ func (ai *MagmawAI) registerSpells() { ai.Target.CurrentTarget = nil // Set add target - if ai.Target.Env.GetNumTargets() > 1 { - addTarget := ai.Target.Env.NextTargetUnit(&ai.Target.Unit) + if ai.Target.Env.ActiveTargetCount() > 1 { + addTarget := ai.Target.Env.NextActiveTargetUnit(&ai.Target.Unit) tankUnit.CurrentTarget = addTarget addTarget.CurrentTarget = tankUnit @@ -308,8 +308,8 @@ func (ai *MagmawAI) registerSpells() { tankUnit.CurrentTarget = &ai.Target.Unit // Remove add target - if ai.Target.Env.GetNumTargets() > 1 { - addTarget := ai.Target.Env.NextTargetUnit(&ai.Target.Unit) + if ai.Target.Env.ActiveTargetCount() > 1 { + addTarget := ai.Target.Env.NextActiveTargetUnit(&ai.Target.Unit) addTarget.AutoAttacks.CancelAutoSwing(sim) addTarget.CurrentTarget = nil } diff --git a/sim/encounters/bwd/nefarian_ai.go b/sim/encounters/bwd/nefarian_ai.go index 16e5af2069..8f9c91e63f 100644 --- a/sim/encounters/bwd/nefarian_ai.go +++ b/sim/encounters/bwd/nefarian_ai.go @@ -169,7 +169,7 @@ func (ai *NefarianAddAI) registerSpells() { }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, addUnit := range sim.Encounter.TargetUnits { + for _, addUnit := range sim.Encounter.ActiveTargetUnits { empowerAura := addUnit.GetAuraByID(empowerActionID) // Assume that the tank is always pre-moving adds before the spark hits them, so that Empower is never refreshed on already active adds. diff --git a/sim/encounters/dragonsoul/blackhorn_ai.go b/sim/encounters/dragonsoul/blackhorn_ai.go index c64e2db568..60477ad42c 100644 --- a/sim/encounters/dragonsoul/blackhorn_ai.go +++ b/sim/encounters/dragonsoul/blackhorn_ai.go @@ -151,10 +151,10 @@ func (ai *BlackhornAI) Initialize(target *core.Target, config *proto.Target) { if ai.isBoss { ai.BossUnit = &target.Unit - ai.AddUnit = &target.NextTarget().Unit + ai.AddUnit = &target.NextActiveTarget().Unit } else { ai.AddUnit = &target.Unit - ai.BossUnit = &target.NextTarget().Unit + ai.BossUnit = &target.NextActiveTarget().Unit } ai.MainTank = ai.BossUnit.CurrentTarget @@ -435,8 +435,7 @@ func (ai *BlackhornAI) registerPowerOfTheAspects() { func (ai *BlackhornAI) Reset(sim *core.Simulation) { // Randomize GCD and swing timings to prevent fake APL-Haste couplings. - ai.Target.ExtendGCDUntil(sim, core.DurationFromSeconds(sim.RandomFloat("Specials Timing")*core.BossGCD.Seconds())) - ai.Target.AutoAttacks.RandomizeMeleeTiming(sim) + ai.Target.Enable(sim) if !ai.isBoss { return @@ -448,7 +447,7 @@ func (ai *BlackhornAI) Reset(sim *core.Simulation) { Priority: core.ActionPriorityDOT, OnAction: func(sim *core.Simulation) { - ai.AddUnit.AutoAttacks.CancelAutoSwing(sim) + sim.DisableTargetUnit(ai.AddUnit) }, }) @@ -460,15 +459,19 @@ func (ai *BlackhornAI) Reset(sim *core.Simulation) { OnAction: func(sim *core.Simulation) { newBossTank := core.Ternary((sim.CurrentTime/ai.tankSwapInterval)%2 == 0, ai.MainTank, ai.OffTank) - ai.swapTargets(sim, ai.BossUnit, newBossTank, true) + ai.swapTargets(sim, ai.BossUnit, newBossTank) ai.Devastate.CD.Set(sim.CurrentTime + core.DurationFromSeconds(sim.RandomFloat("Devastate Timing")*ai.Devastate.CD.Duration.Seconds())) newAddTank := core.Ternary(newBossTank == ai.MainTank, ai.OffTank, ai.MainTank) - ai.swapTargets(sim, ai.AddUnit, newAddTank, sim.CurrentTime < ai.disableAddAt) + ai.swapTargets(sim, ai.AddUnit, newAddTank) }, }) } -func (ai *BlackhornAI) swapTargets(sim *core.Simulation, npc *core.Unit, newTankTarget *core.Unit, enableAutos bool) { +func (ai *BlackhornAI) swapTargets(sim *core.Simulation, npc *core.Unit, newTankTarget *core.Unit) { + if !npc.IsEnabled() { + return + } + npc.AutoAttacks.CancelAutoSwing(sim) npc.CurrentTarget = newTankTarget @@ -476,10 +479,8 @@ func (ai *BlackhornAI) swapTargets(sim *core.Simulation, npc *core.Unit, newTank newTankTarget.CurrentTarget = npc } - if enableAutos { - npc.AutoAttacks.EnableAutoSwing(sim) - npc.AutoAttacks.RandomizeMeleeTiming(sim) - } + npc.AutoAttacks.EnableAutoSwing(sim) + npc.AutoAttacks.RandomizeMeleeTiming(sim) } func (ai *BlackhornAI) ExecuteCustomRotation(sim *core.Simulation) { diff --git a/sim/hunter/explosive_trap.go b/sim/hunter/explosive_trap.go index c211f4e5db..74b85c4c1c 100644 --- a/sim/hunter/explosive_trap.go +++ b/sim/hunter/explosive_trap.go @@ -44,7 +44,7 @@ func (hunter *Hunter) registerExplosiveTrapSpell(timer *core.Timer) { OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { baseDamage := 292 + 0.546*dot.Spell.RangedAttackPower(target) dot.Spell.DamageMultiplierAdditive += bonusPeriodicDamageMultiplier - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.Spell.CalcAndDealPeriodicDamage(sim, aoeTarget, baseDamage/10, dot.Spell.OutcomeRangedHitAndCritNoBlock) } dot.Spell.DamageMultiplierAdditive -= bonusPeriodicDamageMultiplier @@ -63,7 +63,7 @@ func (hunter *Hunter) registerExplosiveTrapSpell(timer *core.Timer) { core.StartDelayedAction(sim, core.DelayedActionOptions{ DoAt: 0, OnAction: func(sim *core.Simulation) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := 292 + (0.0546 * spell.RangedAttackPower(aoeTarget)) baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeRangedHitAndCritNoBlock) @@ -72,7 +72,7 @@ func (hunter *Hunter) registerExplosiveTrapSpell(timer *core.Timer) { }, }) } else { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := 292 + (0.0546 * spell.RangedAttackPower(aoeTarget)) baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeRangedHitAndCritNoBlock) diff --git a/sim/hunter/multi_shot.go b/sim/hunter/multi_shot.go index 05d6b0c932..32791aaa4f 100644 --- a/sim/hunter/multi_shot.go +++ b/sim/hunter/multi_shot.go @@ -35,32 +35,29 @@ func (hunter *Hunter) registerMultiShotSpell() { BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - numHits := hunter.Env.GetNumTargets() // Multi is uncapped in Cata + numHits := hunter.Env.ActiveTargetCount() // Multi is uncapped in Cata sharedDmg := hunter.AutoAttacks.Ranged().BaseDamage(sim) baseDamageArray := make([]*core.SpellResult, numHits) - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - currentTarget := hunter.Env.GetTargetUnit(hitIndex) + for hitIndex, currentTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := sharedDmg + 0.2*spell.RangedAttackPower(currentTarget) baseDamageArray[hitIndex] = spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeRangedHitAndCrit) } spell.WaitTravelTime(sim, func(sim *core.Simulation) { - curTarget := target - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - spell.DealDamage(sim, baseDamageArray[hitIndex]) + for _, result := range baseDamageArray { + spell.DealDamage(sim, result) if hunter.Talents.SerpentSpread > 0 { duration := time.Duration(3+(hunter.Talents.SerpentSpread*3)) * time.Second - ss := hunter.SerpentSting.Dot(curTarget) + ss := hunter.SerpentSting.Dot(result.Target) if hunter.Talents.ImprovedSerpentSting > 0 && (!ss.IsActive() || ss.RemainingDuration(sim) <= duration) { - hunter.ImprovedSerpentSting.Cast(sim, curTarget) + hunter.ImprovedSerpentSting.Cast(sim, result.Target) } ss.BaseTickCount = (3 + (hunter.Talents.SerpentSpread * 3)) / 2 ss.Apply(sim) } - curTarget = sim.Environment.NextTargetUnit(curTarget) } }) diff --git a/sim/hunter/pet.go b/sim/hunter/pet.go index b918f4aa0e..05b29d208b 100644 --- a/sim/hunter/pet.go +++ b/sim/hunter/pet.go @@ -142,7 +142,7 @@ func (hp *HunterPet) ExecuteCustomRotation(sim *core.Simulation) { target := hp.CurrentTarget - if hp.frostStormBreath != nil && hp.frostStormBreath.CanCast(sim, target) && len(sim.Encounter.TargetUnits) > 4 { + if hp.frostStormBreath != nil && hp.frostStormBreath.CanCast(sim, target) && len(sim.Encounter.ActiveTargetUnits) > 4 { hp.frostStormBreath.Cast(sim, target) } diff --git a/sim/hunter/pet_abilities.go b/sim/hunter/pet_abilities.go index e9a933ba52..ba9ec9f134 100644 --- a/sim/hunter/pet_abilities.go +++ b/sim/hunter/pet_abilities.go @@ -307,7 +307,7 @@ func (hp *HunterPet) newFrostStormBreath() *core.Spell { TickLength: time.Second * 2, AffectedByCastSpeed: true, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { frostStormTickSpell.Cast(sim, aoeTarget) } }, @@ -339,7 +339,7 @@ func (hp *HunterPet) newDemoralizingScreech() *core.Spell { School: core.SpellSchoolPhysical, OnSpellHitDealt: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if result.Landed() { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { debuffs.Get(aoeTarget).Activate(sim) } } diff --git a/sim/mage/arcane_explosion.go b/sim/mage/arcane_explosion.go index 3f7b25aec0..d906fe30be 100644 --- a/sim/mage/arcane_explosion.go +++ b/sim/mage/arcane_explosion.go @@ -30,7 +30,7 @@ func (mage *Mage) registerArcaneExplosionSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := 0.368 * mage.ClassSpellScaling baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/mage/blast_wave.go b/sim/mage/blast_wave.go index c4e328fc8e..db98e021ab 100644 --- a/sim/mage/blast_wave.go +++ b/sim/mage/blast_wave.go @@ -36,9 +36,7 @@ func (mage *Mage) registerBlastWaveSpell() { BonusCoefficient: 0.193, ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - var targetCount int32 - for _, aoeTarget := range sim.Encounter.TargetUnits { - targetCount++ + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := sim.Roll(1047, 1233) baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) diff --git a/sim/mage/blizzard.go b/sim/mage/blizzard.go index 7fa08df38f..271105ee8d 100644 --- a/sim/mage/blizzard.go +++ b/sim/mage/blizzard.go @@ -40,7 +40,7 @@ func (mage *Mage) registerBlizzardSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := 0.542 * mage.ClassSpellScaling damage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) if iceShardsProcApplication != nil { iceShardsProcApplication.Cast(sim, aoeTarget) diff --git a/sim/mage/dragons_breath.go b/sim/mage/dragons_breath.go index dd05bba34d..08e9bc1418 100644 --- a/sim/mage/dragons_breath.go +++ b/sim/mage/dragons_breath.go @@ -34,7 +34,7 @@ func (mage *Mage) registerDragonsBreathSpell() { BonusCoefficient: 0.193, ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := 1.378 * mage.ClassSpellScaling baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) diff --git a/sim/mage/flame_orb.go b/sim/mage/flame_orb.go index b2db9b5dbe..8713668473 100644 --- a/sim/mage/flame_orb.go +++ b/sim/mage/flame_orb.go @@ -61,7 +61,7 @@ func (mage *Mage) registerFlameOrbExplodeSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := 1.318 * mage.ClassSpellScaling - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) } @@ -156,7 +156,7 @@ func (fo *FlameOrb) registerFlameOrbTickSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := fo.mageOwner.CalcAndRollDamageRange(sim, 0.278, 0.25) - randomTarget := sim.Encounter.TargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.TargetUnits))))] + randomTarget := sim.Encounter.ActiveTargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.ActiveTargetUnits))))] spell.CalcAndDealDamage(sim, randomTarget, damage, spell.OutcomeMagicHitAndCrit) fo.TickCount += 1 diff --git a/sim/mage/flamestrike.go b/sim/mage/flamestrike.go index ef87df9c79..a9e31c3a27 100644 --- a/sim/mage/flamestrike.go +++ b/sim/mage/flamestrike.go @@ -43,7 +43,7 @@ func (mage *Mage) GetFlameStrikeConfig(spellId int32, isProc bool) core.SpellCon dot.Snapshot(target, baseDamage) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.CalcAndDealPeriodicSnapshotDamage(sim, aoeTarget, dot.OutcomeSnapshotCrit) } }, @@ -51,7 +51,7 @@ func (mage *Mage) GetFlameStrikeConfig(spellId int32, isProc bool) core.SpellCon }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := 0.662 * mage.ClassSpellScaling baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) diff --git a/sim/mage/freeze.go b/sim/mage/freeze.go index 1ec41b2636..1284b65fb3 100644 --- a/sim/mage/freeze.go +++ b/sim/mage/freeze.go @@ -31,7 +31,7 @@ func (mage *Mage) registerFreezeSpell() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := 0.409 * mage.ClassSpellScaling baseDamage *= sim.Encounter.AOECapMultiplier() spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) diff --git a/sim/mage/frostfire_orb.go b/sim/mage/frostfire_orb.go index bbfde416fe..41601680ba 100644 --- a/sim/mage/frostfire_orb.go +++ b/sim/mage/frostfire_orb.go @@ -122,7 +122,7 @@ func (ffo *FrostfireOrb) registerFrostfireOrbTickSpell() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := 0.278 * ffo.mageOwner.ClassSpellScaling - randomTarget := sim.Encounter.TargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.TargetUnits))))] + randomTarget := sim.Encounter.ActiveTargetUnits[int(sim.Roll(0, float64(len(sim.Encounter.ActiveTargetUnits))))] spell.CalcAndDealDamage(sim, randomTarget, damage, spell.OutcomeMagicHitAndCrit) ffo.TickCount += 1 if ffo.TickCount == 15 { diff --git a/sim/mage/living_bomb.go b/sim/mage/living_bomb.go index 378fd16e7e..08cbff3c1c 100644 --- a/sim/mage/living_bomb.go +++ b/sim/mage/living_bomb.go @@ -39,7 +39,7 @@ func (mage *Mage) registerLivingBombSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := 0.5 * mage.ClassSpellScaling baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/mage/talents_fire.go b/sim/mage/talents_fire.go index b86c8be06d..58b38869d1 100644 --- a/sim/mage/talents_fire.go +++ b/sim/mage/talents_fire.go @@ -79,7 +79,7 @@ func (mage *Mage) ApplyFireTalents() { ClassSpellMask: MageSpellBlastWave, ICD: time.Millisecond * 1, ExtraCondition: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) bool { - return len(spell.Unit.Env.Encounter.Targets) >= 2 + return len(spell.Unit.Env.Encounter.ActiveTargets) >= 2 }, Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { flameStrikeCopy := spell.Unit.GetSpell(core.ActionID{SpellID: 88148, Tag: 1}) @@ -280,7 +280,7 @@ func (mage *Mage) applyPyromaniac() { }, }) - if len(mage.Env.Encounter.TargetUnits) < 3 { + if len(mage.Env.Encounter.AllTargetUnits) < 3 { return } @@ -291,7 +291,7 @@ func (mage *Mage) applyPyromaniac() { OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { dotSpells := []*core.Spell{mage.LivingBomb, mage.Ignite, mage.Pyroblast, mage.Combustion} activeDotTargets := 0 - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { for _, spells := range dotSpells { if spells.Dot(aoeTarget).IsActive() { activeDotTargets++ @@ -331,7 +331,7 @@ func (mage *Mage) applyMoltenFury() { mage.RegisterResetEffect(func(sim *core.Simulation) { sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { if isExecute == 35 { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.AllTargetUnits { moltenFuryAuras.Get(aoeTarget).Activate(sim) } } @@ -398,7 +398,7 @@ func (mage *Mage) applyImpact() { originalTarget := mage.CurrentTarget - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { if aoeTarget == originalTarget { continue } diff --git a/sim/paladin/consecration.go b/sim/paladin/consecration.go index 7e12e35e70..e6dc0074ba 100644 --- a/sim/paladin/consecration.go +++ b/sim/paladin/consecration.go @@ -55,7 +55,7 @@ func (paladin *Paladin) registerConsecrationSpell() { baseDamage := consAvgDamage + 0.0270000007*dot.Spell.MeleeAttackPower() + 0.0270000007*dot.Spell.SpellPower() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.Spell.CalcAndDealPeriodicDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/paladin/guardian_of_ancient_kings.go b/sim/paladin/guardian_of_ancient_kings.go index c940b44472..fc38f8c024 100644 --- a/sim/paladin/guardian_of_ancient_kings.go +++ b/sim/paladin/guardian_of_ancient_kings.go @@ -139,8 +139,8 @@ func (paladin *Paladin) registerRetributionGuardian(duration time.Duration) *cor ancientFuryMinDamage, ancientFuryMaxDamage := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassPaladin, 0.23659999669, 0.30000001192) - numTargets := paladin.Env.GetNumTargets() - results := make([]*core.SpellResult, numTargets) + maxTargets := paladin.Env.TotalTargetCount() + results := make([]*core.SpellResult, maxTargets) ancientFury := paladin.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 86704}, @@ -168,11 +168,11 @@ func (paladin *Paladin) registerRetributionGuardian(duration time.Duration) *cor // Deals X Holy damage per application of Ancient Power, // divided evenly among all targets within 10 yards. baseDamage *= float64(paladin.AncientPowerAura.GetStacks()) + numTargets := sim.Environment.ActiveTargetCount() baseDamage /= float64(numTargets) - for idx := int32(0); idx < numTargets; idx++ { - currentTarget := sim.Environment.GetTargetUnit(idx) - results[idx] = spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + for idx, currentTarget := range sim.Environment.GetActiveTargets() { + results[idx] = spell.CalcDamage(sim, ¤tTarget.Unit, baseDamage, spell.OutcomeMagicHitAndCrit) } for idx := int32(0); idx < numTargets; idx++ { diff --git a/sim/paladin/holy_wrath.go b/sim/paladin/holy_wrath.go index 995c24d1c5..d71772ddec 100644 --- a/sim/paladin/holy_wrath.go +++ b/sim/paladin/holy_wrath.go @@ -9,7 +9,6 @@ import ( func (paladin *Paladin) registerHolyWrath() { hwAvgDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassPaladin, 2.33299994469) - numTargets := paladin.Env.GetNumTargets() paladin.HolyWrath = paladin.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 2812}, @@ -39,15 +38,15 @@ func (paladin *Paladin) registerHolyWrath() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + numTargets := sim.Environment.ActiveTargetCount() results := make([]*core.SpellResult, numTargets) baseDamage := hwAvgDamage + .61*spell.SpellPower() // Damage is split between all mobs, each hit rolls for hit/crit separately baseDamage /= float64(numTargets) - for idx := int32(0); idx < numTargets; idx++ { - currentTarget := sim.Environment.GetTargetUnit(idx) - results[idx] = spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + for idx, currentTarget := range sim.Environment.GetActiveTargets() { + results[idx] = spell.CalcDamage(sim, ¤tTarget.Unit, baseDamage, spell.OutcomeMagicHitAndCrit) } spell.WaitTravelTime(sim, func(simulation *core.Simulation) { diff --git a/sim/paladin/protection/avengers_shield.go b/sim/paladin/protection/avengers_shield.go index 90a7b68181..e0005292de 100644 --- a/sim/paladin/protection/avengers_shield.go +++ b/sim/paladin/protection/avengers_shield.go @@ -14,8 +14,8 @@ func (prot *ProtectionPaladin) registerAvengersShieldSpell() { glyphedSingleTargetAS := prot.HasMajorGlyph(proto.PaladinMajorGlyph_GlyphOfFocusedShield) // Glyph to single target, OR apply to up to 3 targets - numTargets := core.TernaryInt32(glyphedSingleTargetAS, 1, min(3, prot.Env.GetNumTargets())) - results := make([]*core.SpellResult, numTargets) + maxTargets := core.TernaryInt32(glyphedSingleTargetAS, 1, min(3, prot.Env.TotalTargetCount())) + results := make([]*core.SpellResult, maxTargets) prot.AvengersShield = prot.RegisterSpell(core.SpellConfig{ ActionID: actionId, @@ -46,12 +46,12 @@ func (prot *ProtectionPaladin) registerAvengersShieldSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { constBaseDamage := 0.20999999344*spell.SpellPower() + 0.41899999976*spell.MeleeAttackPower() + numTargets := min(maxTargets, sim.Environment.ActiveTargetCount()) for idx := int32(0); idx < numTargets; idx++ { baseDamage := constBaseDamage + sim.RollWithLabel(asMinDamage, asMaxDamage, "Avengers Shield"+prot.Label) - - currentTarget := sim.Environment.GetTargetUnit(idx) - results[idx] = spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + results[idx] = spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + target = sim.Environment.NextActiveTargetUnit(target) } for idx := int32(0); idx < numTargets; idx++ { diff --git a/sim/paladin/seal_of_righteousness.go b/sim/paladin/seal_of_righteousness.go index f8f45da969..e5db05bd26 100644 --- a/sim/paladin/seal_of_righteousness.go +++ b/sim/paladin/seal_of_righteousness.go @@ -7,13 +7,13 @@ import ( ) func (paladin *Paladin) registerSealOfRighteousness() { - var numTargets int32 + var maxTargets int32 if paladin.Talents.SealsOfCommand { - numTargets = paladin.Env.GetNumTargets() + maxTargets = paladin.Env.TotalTargetCount() } else { - numTargets = 1 + maxTargets = 1 } - results := make([]*core.SpellResult, numTargets) + results := make([]*core.SpellResult, maxTargets) // Judgement of Righteousness cast on Judgement paladin.JudgementOfRighteousness = paladin.RegisterSpell(core.SpellConfig{ @@ -74,12 +74,12 @@ func (paladin *Paladin) registerSealOfRighteousness() { baseDamage := paladin.GetMHWeapon().SwingSpeed * (0.022*spell.SpellPower() + 0.011*spell.MeleeAttackPower()) - for idx := int32(0); idx < numTargets; idx++ { + for idx, targetUnit := range sim.Encounter.ActiveTargetUnits { // can't miss if melee swing landed, but can crit - results[idx] = spell.CalcDamage(sim, sim.Environment.GetTargetUnit(idx), baseDamage, spell.OutcomeMeleeSpecialCritOnly) + results[idx] = spell.CalcDamage(sim, targetUnit, baseDamage, spell.OutcomeMeleeSpecialCritOnly) } - for idx := int32(0); idx < numTargets; idx++ { + for idx := range sim.Encounter.ActiveTargetUnits { spell.DealDamage(sim, results[idx]) } }) diff --git a/sim/paladin/talents_protection.go b/sim/paladin/talents_protection.go index 0d74231caf..200776a844 100644 --- a/sim/paladin/talents_protection.go +++ b/sim/paladin/talents_protection.go @@ -94,7 +94,6 @@ func (paladin *Paladin) applyHammerOfTheRighteous() { aoeMinDamage, aoeMaxDamage := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassPaladin, 0.70800000429, 0.40000000596) - numTargets := paladin.Env.GetNumTargets() actionId := core.ActionID{SpellID: 53595} hpMetrics := paladin.NewHolyPowerMetrics(actionId) @@ -120,11 +119,11 @@ func (paladin *Paladin) applyHammerOfTheRighteous() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.RollWithLabel(aoeMinDamage, aoeMaxDamage, "Hammer of the Righteous"+paladin.Label) + 0.18000000715*spell.MeleeAttackPower() + numTargets := sim.Environment.ActiveTargetCount() results := make([]*core.SpellResult, numTargets) - for idx := int32(0); idx < numTargets; idx++ { - currentTarget := sim.Environment.GetTargetUnit(idx) - results[idx] = spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeMagicCrit) + for idx, currentTarget := range sim.Environment.GetActiveTargets() { + results[idx] = spell.CalcDamage(sim, ¤tTarget.Unit, baseDamage, spell.OutcomeMagicCrit) } for idx := int32(0); idx < numTargets; idx++ { diff --git a/sim/paladin/talents_retribution.go b/sim/paladin/talents_retribution.go index 15c40ad863..e384927547 100644 --- a/sim/paladin/talents_retribution.go +++ b/sim/paladin/talents_retribution.go @@ -221,7 +221,6 @@ func (paladin *Paladin) applyDivineStorm() { return } - numTargets := paladin.Env.GetNumTargets() actionId := core.ActionID{SpellID: 53385} hpMetrics := paladin.NewHolyPowerMetrics(actionId) @@ -254,12 +253,12 @@ func (paladin *Paladin) applyDivineStorm() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { numHits := 0 + numTargets := sim.Environment.ActiveTargetCount() results := make([]*core.SpellResult, numTargets) - for idx := int32(0); idx < numTargets; idx++ { - currentTarget := sim.Environment.GetTargetUnit(idx) + for idx, currentTarget := range sim.Environment.GetActiveTargets() { baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) - result := spell.CalcDamage(sim, currentTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + result := spell.CalcDamage(sim, ¤tTarget.Unit, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) if result.Landed() { numHits += 1 } diff --git a/sim/priest/mind_sear.go b/sim/priest/mind_sear.go index ae4be62207..e7b64ae894 100644 --- a/sim/priest/mind_sear.go +++ b/sim/priest/mind_sear.go @@ -24,7 +24,7 @@ func (priest *Priest) getMindSearTickSpell() *core.Spell { config.ActionID = core.ActionID{SpellID: 48045} config.ApplyEffects = func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := priest.ClassSpellScaling * 0.23 - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { // Calc spell damage but deal as periodic for metric purposes result := spell.CalcDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCritNoHitCounter) diff --git a/sim/rogue/combat/blade_flurry.go b/sim/rogue/combat/blade_flurry.go index e5631700e9..32277d3382 100644 --- a/sim/rogue/combat/blade_flurry.go +++ b/sim/rogue/combat/blade_flurry.go @@ -41,7 +41,7 @@ func (comRogue *CombatRogue) registerBladeFlurry() { comRogue.ApplyAdditiveEnergyRegenBonus(sim, -energyReduction) }, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if sim.GetNumTargets() < 2 { + if sim.ActiveTargetCount() < 2 { return } if result.Damage == 0 || !spell.ProcMask.Matches(core.ProcMaskMelee) { @@ -55,7 +55,7 @@ func (comRogue *CombatRogue) registerBladeFlurry() { // Undo armor reduction to get the raw damage value. curDmg = result.Damage / result.ResistanceMultiplier - bfTarget := comRogue.Env.NextTargetUnit(result.Target) + bfTarget := comRogue.Env.NextActiveTargetUnit(result.Target) bfHit.Cast(sim, bfTarget) }, }) diff --git a/sim/rogue/combat/killing_spree.go b/sim/rogue/combat/killing_spree.go index cd6207ecb5..322e3e756c 100644 --- a/sim/rogue/combat/killing_spree.go +++ b/sim/rogue/combat/killing_spree.go @@ -69,11 +69,11 @@ func (comRogue *CombatRogue) registerKillingSpreeCD() { NumTicks: 5, TickImmediately: true, OnAction: func(s *core.Simulation) { - targetCount := sim.GetNumTargets() + targetCount := sim.ActiveTargetCount() target := comRogue.CurrentTarget if targetCount > 1 { newUnitIndex := int32(math.Ceil(float64(targetCount)*sim.RandomFloat("Killing Spree"))) - 1 - target = sim.GetTargetUnit(newUnitIndex) + target = sim.Encounter.ActiveTargetUnits[newUnitIndex] } mhWeaponSwing.Cast(sim, target) ohWeaponSwing.Cast(sim, target) diff --git a/sim/rogue/fan_of_knives.go b/sim/rogue/fan_of_knives.go index 94fa7f4fb3..e831dc9454 100644 --- a/sim/rogue/fan_of_knives.go +++ b/sim/rogue/fan_of_knives.go @@ -20,7 +20,7 @@ func (rogue *Rogue) registerFanOfKnives() { ThreatMultiplier: 1, }) - results := make([]*core.SpellResult, len(rogue.Env.Encounter.TargetUnits)) + results := make([]*core.SpellResult, len(rogue.Env.Encounter.AllTargetUnits)) poisonProcModifier := []float64{0.0, 0.33, 0.67, 1.0}[rogue.Talents.VilePoisons] rogue.FanOfKnives = rogue.RegisterSpell(core.SpellConfig{ @@ -43,14 +43,14 @@ func (rogue *Rogue) registerFanOfKnives() { ApplyEffects: func(sim *core.Simulation, unit *core.Unit, spell *core.Spell) { rogue.BreakStealth(sim) - for i, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := fokSpell.Unit.RangedWeaponDamage(sim, fokSpell.RangedAttackPower(aoeTarget)) baseDamage *= sim.Encounter.AOECapMultiplier() - results[i] = fokSpell.CalcDamage(sim, aoeTarget, baseDamage, fokSpell.OutcomeRangedHitAndCrit) + results[aoeTarget.Index] = fokSpell.CalcDamage(sim, aoeTarget, baseDamage, fokSpell.OutcomeRangedHitAndCrit) } - for i, aoeTarget := range sim.Encounter.TargetUnits { - fokSpell.DealDamage(sim, results[i]) + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { + fokSpell.DealDamage(sim, results[aoeTarget.Index]) if rogue.Talents.VilePoisons > 0 { mhProcChance := poisonProcModifier * getPoisonsProcChance(core.ProcMaskMeleeMH, rogue.Options.MhImbue, proto.ItemSlot_ItemSlotMainHand, rogue) diff --git a/sim/rogue/subtlety/sanguinary_vein.go b/sim/rogue/subtlety/sanguinary_vein.go index 4f017f5f94..dd58c973a7 100644 --- a/sim/rogue/subtlety/sanguinary_vein.go +++ b/sim/rogue/subtlety/sanguinary_vein.go @@ -54,7 +54,7 @@ func (subRogue *SubtletyRogue) registerSanguinaryVein() { subRogue.RegisterPrepullAction(-1, func(sim *core.Simulation) { if subRogue.Options.AssumeBleedActive { - for _, target := range subRogue.Env.Encounter.TargetUnits { + for _, target := range subRogue.Env.Encounter.AllTargetUnits { aura := svDebuffArray.Get(target) aura.Duration = core.NeverExpires aura.Activate(sim) diff --git a/sim/shaman/chain_lightning.go b/sim/shaman/chain_lightning.go index e95528dced..d50e5c1dc7 100644 --- a/sim/shaman/chain_lightning.go +++ b/sim/shaman/chain_lightning.go @@ -8,10 +8,10 @@ import ( ) func (shaman *Shaman) registerChainLightningSpell() { - numHits := min(core.TernaryInt32(shaman.HasMajorGlyph(proto.ShamanMajorGlyph_GlyphOfChainLightning), 5, 3), shaman.Env.GetNumTargets()) + maxHits := min(core.TernaryInt32(shaman.HasMajorGlyph(proto.ShamanMajorGlyph_GlyphOfChainLightning), 5, 3), shaman.Env.TotalTargetCount()) shaman.ChainLightning = shaman.newChainLightningSpell(false) - shaman.ChainLightningOverloads = []*core.Spell{} - for i := int32(0); i < numHits; i++ { + shaman.ChainLightningOverloads = make([]*core.Spell, 0, maxHits) + for range maxHits { shaman.ChainLightningOverloads = append(shaman.ChainLightningOverloads, shaman.newChainLightningSpell(true)) } } @@ -32,7 +32,7 @@ func (shaman *Shaman) newChainLightningSpell(isElementalOverload bool) *core.Spe spellConfig.DamageMultiplier *= 0.90 numHits += 2 } - numHits = min(numHits, shaman.Env.GetNumTargets()) + numHits = min(numHits, shaman.Env.ActiveTargetCount()) spellConfig.ApplyEffects = func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { bounceReduction := core.TernaryFloat64(shaman.DungeonSet3.IsActive() && !isElementalOverload, 0.83, 0.7) @@ -45,7 +45,7 @@ func (shaman *Shaman) newChainLightningSpell(isElementalOverload bool) *core.Spe for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { results[hitIndex] = shaman.calcDamageStormstrikeCritChance(sim, curTarget, baseDamage, spell) - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) spell.DamageMultiplier *= bounceReduction } diff --git a/sim/shaman/earthquake.go b/sim/shaman/earthquake.go index bca940aff6..12cab031e7 100644 --- a/sim/shaman/earthquake.go +++ b/sim/shaman/earthquake.go @@ -21,7 +21,7 @@ func (shaman *Shaman) registerEarthquakeSpell() { spell.SpellMetrics[target.UnitIndex].Casts-- // Do not count pulses as casts // Coefficient damage calculated manually because it's a Nature spell but deals Physical damage baseDamage := shaman.ClassSpellScaling*0.32400000095 + 0.11*spell.SpellPower() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/shaman/elemental/thunderstorm.go b/sim/shaman/elemental/thunderstorm.go index 7cb9986e9b..be95137dbc 100644 --- a/sim/shaman/elemental/thunderstorm.go +++ b/sim/shaman/elemental/thunderstorm.go @@ -44,14 +44,14 @@ func (elemental *ElementalShaman) registerThunderstormSpell() { elemental.AddMana(sim, elemental.MaxMana()*manaRestore, manaMetrics) if elemental.Shaman.ThunderstormInRange { - results := make([]*core.SpellResult, elemental.Env.GetNumTargets()) + results := make([]*core.SpellResult, elemental.Env.ActiveTargetCount()) baseDamage := elemental.GetShaman().CalcAndRollDamageRange(sim, 1.62999999523, 0.13300000131) aoeMult := sim.Encounter.AOECapMultiplier() spell.DamageMultiplier *= aoeMult - for i, aoeTarget := range sim.Encounter.TargetUnits { + for i, aoeTarget := range sim.Encounter.ActiveTargetUnits { results[i] = spell.CalcDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } - for i, _ := range sim.Encounter.TargetUnits { + for i, _ := range sim.Encounter.ActiveTargetUnits { spell.DealDamage(sim, results[i]) } spell.DamageMultiplier /= aoeMult diff --git a/sim/shaman/enhancement/lavalash.go b/sim/shaman/enhancement/lavalash.go index 385e1d543f..0060009f67 100644 --- a/sim/shaman/enhancement/lavalash.go +++ b/sim/shaman/enhancement/lavalash.go @@ -63,9 +63,9 @@ func (enh *EnhancementShaman) registerLavaLashSpell() { if flameShockDot != nil && flameShockDot.IsActive() { numberSpread := 0 - maxTargets := min(4, len(sim.Encounter.TargetUnits)) + maxTargets := min(4, len(sim.Encounter.ActiveTargetUnits)) - validTargets := core.FilterSlice(sim.Encounter.TargetUnits, func(unit *core.Unit) bool { + validTargets := core.FilterSlice(sim.Encounter.ActiveTargetUnits, func(unit *core.Unit) bool { dot := enh.FlameShock.Dot(unit) return dot == nil || !dot.IsActive() }) diff --git a/sim/shaman/fire_elemental_spells.go b/sim/shaman/fire_elemental_spells.go index 2d45472fde..3e0ed8a97e 100644 --- a/sim/shaman/fire_elemental_spells.go +++ b/sim/shaman/fire_elemental_spells.go @@ -64,7 +64,7 @@ func (fireElemental *FireElemental) registerFireNova() { BonusCoefficient: 1.00, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := sim.Roll(453, 537) * sim.Encounter.AOECapMultiplier() //Estimated from beta testing spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } @@ -96,7 +96,7 @@ func (fireElemental *FireElemental) registerFireShieldAura() { OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { // TODO is this the right affect should it be Capped? // TODO these are approximation, from base SP - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { //baseDamage *= sim.Encounter.AOECapMultiplier() dot.Spell.CalcAndDealDamage(sim, aoeTarget, 102, dot.Spell.OutcomeMagicHitAndCrit) //Estimated from beta testing } diff --git a/sim/shaman/fire_totems.go b/sim/shaman/fire_totems.go index 7d01400b3c..d375e52015 100644 --- a/sim/shaman/fire_totems.go +++ b/sim/shaman/fire_totems.go @@ -56,14 +56,14 @@ func (shaman *Shaman) registerSearingTotemSpell() { Priority: core.ActionPriorityGCD, OnAction: func(sim *core.Simulation) { - spell.Dot(sim.GetTargetUnit(0)).BaseTickCount = searingTickCount(shaman, dropTime.Minutes()) - spell.Dot(sim.GetTargetUnit(0)).Apply(sim) + spell.Dot(sim.Encounter.ActiveTargetUnits[0]).BaseTickCount = searingTickCount(shaman, dropTime.Minutes()) + spell.Dot(sim.Encounter.ActiveTargetUnits[0]).Apply(sim) }, } sim.AddPendingAction(pa) } else { - spell.Dot(sim.GetTargetUnit(0)).BaseTickCount = searingTickCount(shaman, 0) - spell.Dot(sim.GetTargetUnit(0)).Apply(sim) + spell.Dot(sim.Encounter.ActiveTargetUnits[0]).BaseTickCount = searingTickCount(shaman, 0) + spell.Dot(sim.Encounter.ActiveTargetUnits[0]).Apply(sim) } duration := 60 * (1.0 + 0.20*float64(shaman.Talents.TotemicFocus)) shaman.TotemExpirations[FireTotem] = sim.CurrentTime + time.Duration(duration)*time.Second @@ -101,14 +101,14 @@ func (shaman *Shaman) registerMagmaTotemSpell() { BonusCoefficient: 0.06700000167, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - results := make([]*core.SpellResult, shaman.Env.GetNumTargets()) + results := make([]*core.SpellResult, shaman.Env.ActiveTargetCount()) baseDamage := shaman.CalcScalingSpellDmg(0.26699998975) aoeMult := sim.Encounter.AOECapMultiplier() dot.Spell.DamageMultiplier *= aoeMult - for i, aoeTarget := range sim.Encounter.TargetUnits { + for i, aoeTarget := range sim.Encounter.ActiveTargetUnits { results[i] = dot.Spell.CalcPeriodicDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) } - for i := range sim.Encounter.TargetUnits { + for i := range sim.Encounter.ActiveTargetUnits { dot.Spell.DealPeriodicDamage(sim, results[i]) } dot.Spell.DamageMultiplier /= aoeMult diff --git a/sim/shaman/firenova.go b/sim/shaman/firenova.go index b02b1b4657..976905ba7f 100644 --- a/sim/shaman/firenova.go +++ b/sim/shaman/firenova.go @@ -32,21 +32,21 @@ func (shaman *Shaman) registerFireNovaSpell() { ThreatMultiplier: 1, BonusCoefficient: 0.164, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - results := make([][]*core.SpellResult, shaman.Env.GetNumTargets()) + results := make([][]*core.SpellResult, shaman.Env.ActiveTargetCount()) baseDamage := shaman.CalcAndRollDamageRange(sim, 0.78500002623, 0.11200000346) - for i, aoeTarget := range sim.Encounter.TargetUnits { + for i, aoeTarget := range sim.Encounter.ActiveTargetUnits { if shaman.FlameShock.Dot(aoeTarget).IsActive() { - results[i] = make([]*core.SpellResult, shaman.Env.GetNumTargets()) - for j, newTarget := range sim.Encounter.TargetUnits { + results[i] = make([]*core.SpellResult, shaman.Env.ActiveTargetCount()) + for j, newTarget := range sim.Encounter.ActiveTargetUnits { if newTarget != aoeTarget { results[i][j] = spell.CalcDamage(sim, newTarget, baseDamage, spell.OutcomeMagicHitAndCrit) } } } } - for i, aoeTarget := range sim.Encounter.TargetUnits { + for i, aoeTarget := range sim.Encounter.ActiveTargetUnits { if shaman.FlameShock.Dot(aoeTarget).IsActive() { - for j, newTarget := range sim.Encounter.TargetUnits { + for j, newTarget := range sim.Encounter.ActiveTargetUnits { if newTarget != aoeTarget { spell.DealDamage(sim, results[i][j]) } @@ -55,7 +55,7 @@ func (shaman *Shaman) registerFireNovaSpell() { } }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { if shaman.FlameShock.Dot(aoeTarget).IsActive() { return true } diff --git a/sim/warlock/demonology/hand_of_guldan.go b/sim/warlock/demonology/hand_of_guldan.go index c44c9b52b3..f5729d4b46 100644 --- a/sim/warlock/demonology/hand_of_guldan.go +++ b/sim/warlock/demonology/hand_of_guldan.go @@ -61,7 +61,7 @@ func (demonology *DemonologyWarlock) registerHandOfGuldan() { baseDamage := demonology.CalcAndRollDamageRange(sim, 1.59300005436, 0.16599999368) result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) if result.Landed() { - for _, target := range sim.Encounter.TargetUnits { + for _, target := range sim.Encounter.ActiveTargetUnits { curseOfGuldanAuras.Get(target).Activate(sim) } } diff --git a/sim/warlock/demonology/metamorphosis.go b/sim/warlock/demonology/metamorphosis.go index 066f91fa09..a0552fadb2 100644 --- a/sim/warlock/demonology/metamorphosis.go +++ b/sim/warlock/demonology/metamorphosis.go @@ -104,7 +104,7 @@ func (demonology *DemonologyWarlock) registerMetamorphosis() { OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { baseDmg := demonology.CalcScalingSpellDmg(0.58899998665) * sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.Spell.CalcAndDealDamage(sim, aoeTarget, baseDmg, dot.Spell.OutcomeMagicHit) } }, diff --git a/sim/warlock/infernal.go b/sim/warlock/infernal.go index d244bcabaa..95466e5006 100644 --- a/sim/warlock/infernal.go +++ b/sim/warlock/infernal.go @@ -41,7 +41,7 @@ func (warlock *Warlock) registerSummonInfernal(timer *core.Timer) { BonusCoefficient: 0.76499998569, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { baseDamage := sim.Encounter.AOECapMultiplier() * warlock.CalcAndRollDamageRange(sim, 0.48500001431, 0.11999999732) spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) @@ -146,7 +146,7 @@ func (infernal *InfernalPet) Initialize() { warlockSP := infernal.owner.Unit.GetStat(stats.SpellPower) baseDmg := (40 + warlockSP*0.2) * sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { dot.Spell.CalcAndDealDamage(sim, aoeTarget, baseDmg, dot.Spell.OutcomeMagicHit) } }, diff --git a/sim/warlock/pets.go b/sim/warlock/pets.go index be8327e48f..45a7b5e845 100644 --- a/sim/warlock/pets.go +++ b/sim/warlock/pets.go @@ -264,7 +264,7 @@ func (pet *WarlockPet) registerFelstormSpell() { baseDmg := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) baseDmg += pet.Owner.CalcScalingSpellDmg(0.1155000031) + 0.231*spell.MeleeAttackPower() - for _, target := range sim.Encounter.TargetUnits { + for _, target := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, target, baseDmg, spell.OutcomeMeleeWeaponSpecialHitAndCrit) } }, @@ -278,8 +278,6 @@ func (pet *WarlockPet) registerFelstormSpell() { } func (pet *WarlockPet) registerLegionStrikeSpell() { - numberOfTargets := pet.Env.GetNumTargets() - pet.AutoCastAbilities = append(pet.AutoCastAbilities, pet.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 30213}, SpellSchool: core.SpellSchoolPhysical, @@ -306,9 +304,9 @@ func (pet *WarlockPet) registerLegionStrikeSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDmg := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) baseDmg += pet.Owner.CalcScalingSpellDmg(0.1439999938) + 0.264*spell.MeleeAttackPower() - baseDmg /= float64(numberOfTargets) + baseDmg /= float64(sim.Environment.ActiveTargetCount()) - for _, target := range sim.Encounter.TargetUnits { + for _, target := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, target, baseDmg, spell.OutcomeMeleeWeaponSpecialHitAndCrit) } }, diff --git a/sim/warlock/seed.go b/sim/warlock/seed.go index ea01922042..ae47e818bb 100644 --- a/sim/warlock/seed.go +++ b/sim/warlock/seed.go @@ -23,7 +23,7 @@ func (warlock *Warlock) registerSeed() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDmg := warlock.CalcAndRollDamageRange(sim, 0.76560002565, 0.15000000596) * sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDmg, spell.OutcomeMagicHitAndCrit) } }, diff --git a/sim/warrior/arms/bladestorm.go b/sim/warrior/arms/bladestorm.go index a5d2a82ece..0ab795dec2 100644 --- a/sim/warrior/arms/bladestorm.go +++ b/sim/warrior/arms/bladestorm.go @@ -12,8 +12,8 @@ func (war *ArmsWarrior) RegisterBladestorm() { return } actionID := core.ActionID{SpellID: 46924} - numHits := war.Env.GetNumTargets() // 1 hit per target - results := make([]*core.SpellResult, numHits) + maxHits := war.Env.TotalTargetCount() // 1 hit per target + results := make([]*core.SpellResult, maxHits) bladestorm := war.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -50,15 +50,14 @@ func (war *ArmsWarrior) RegisterBladestorm() { target := war.CurrentTarget spell := dot.Spell curTarget := target + numHits := sim.Environment.ActiveTargetCount() for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { baseDamage := 1.5 * spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) results[hitIndex] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } - curTarget = target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { spell.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) } }, }, diff --git a/sim/warrior/arms/sweeping_strikes.go b/sim/warrior/arms/sweeping_strikes.go index 7c3a94bfe9..1a31e9af26 100644 --- a/sim/warrior/arms/sweeping_strikes.go +++ b/sim/warrior/arms/sweeping_strikes.go @@ -34,7 +34,7 @@ func (war *ArmsWarrior) RegisterSweepingStrikes() { ActionID: actionID, Duration: time.Second * 10, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Damage <= 0 || !spell.ProcMask.Matches(core.ProcMaskMelee) || war.Env.GetNumTargets() < 2 { + if result.Damage <= 0 || !spell.ProcMask.Matches(core.ProcMaskMelee) || war.Env.ActiveTargetCount() < 2 { return } @@ -47,7 +47,7 @@ func (war *ArmsWarrior) RegisterSweepingStrikes() { // Undo armor reduction to get the raw damage value. curDmg /= result.ResistanceMultiplier - ssHit.Cast(sim, war.Env.NextTargetUnit(result.Target)) + ssHit.Cast(sim, war.Env.NextActiveTargetUnit(result.Target)) ssHit.SpellMetrics[result.Target.UnitIndex].Casts-- }, }) diff --git a/sim/warrior/demoralizing_shout.go b/sim/warrior/demoralizing_shout.go index 5554440c8e..44da175690 100644 --- a/sim/warrior/demoralizing_shout.go +++ b/sim/warrior/demoralizing_shout.go @@ -31,7 +31,7 @@ func (warrior *Warrior) RegisterDemoralizingShoutSpell() { FlatThreatBonus: 63.2, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := spell.CalcAndDealOutcome(sim, aoeTarget, spell.OutcomeMagicHit) if result.Landed() { warrior.DemoralizingShoutAuras.Get(aoeTarget).Activate(sim) diff --git a/sim/warrior/fury/TestFury.results b/sim/warrior/fury/TestFury.results index 7abc0ff532..4d5ea53515 100644 --- a/sim/warrior/fury/TestFury.results +++ b/sim/warrior/fury/TestFury.results @@ -11,7 +11,7 @@ character_stats_results: { final_stats: 412 final_stats: 692 final_stats: 0 - final_stats: 2731.66357 + final_stats: 2731.66358 final_stats: 1315 final_stats: 25247.454 final_stats: 209 diff --git a/sim/warrior/heroic_leap.go b/sim/warrior/heroic_leap.go index f8751f10fe..29563eea9b 100644 --- a/sim/warrior/heroic_leap.go +++ b/sim/warrior/heroic_leap.go @@ -8,8 +8,8 @@ import ( func (warrior *Warrior) RegisterHeroicLeap() { - numHits := warrior.Env.GetNumTargets() - results := make([]*core.SpellResult, numHits) + maxHits := warrior.Env.TotalTargetCount() + results := make([]*core.SpellResult, maxHits) warrior.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 6544}, @@ -40,10 +40,11 @@ func (warrior *Warrior) RegisterHeroicLeap() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := 1 + 0.5*spell.MeleeAttackPower() curTarget := target + numHits := sim.Environment.ActiveTargetCount() for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { results[hitIndex] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { diff --git a/sim/warrior/heroic_strike_cleave.go b/sim/warrior/heroic_strike_cleave.go index c35b734fb0..b846fa9c27 100644 --- a/sim/warrior/heroic_strike_cleave.go +++ b/sim/warrior/heroic_strike_cleave.go @@ -54,8 +54,8 @@ func (warrior *Warrior) RegisterHeroicStrikeSpell() { func (warrior *Warrior) RegisterCleaveSpell() { targets := core.TernaryInt32(warrior.HasMajorGlyph(proto.WarriorMajorGlyph_GlyphOfCleaving), 3, 2) - numHits := min(targets, warrior.Env.GetNumTargets()) - results := make([]*core.SpellResult, numHits) + maxHits := min(targets, warrior.Env.TotalTargetCount()) + results := make([]*core.SpellResult, maxHits) warrior.Cleave = warrior.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 845}, @@ -86,17 +86,16 @@ func (warrior *Warrior) RegisterCleaveSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { curTarget := target + numHits := min(maxHits, sim.Environment.ActiveTargetCount()) for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { baseDamage := 6 + (spell.MeleeAttackPower() * 0.45) results[hitIndex] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } - curTarget = target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { spell.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) } }, }) diff --git a/sim/warrior/protection/shockwave.go b/sim/warrior/protection/shockwave.go index e6da1e152e..74a666da38 100644 --- a/sim/warrior/protection/shockwave.go +++ b/sim/warrior/protection/shockwave.go @@ -40,7 +40,7 @@ func (war *ProtectionWarrior) RegisterShockwave() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := 0.75 * spell.MeleeAttackPower() baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) } }, diff --git a/sim/warrior/revenge.go b/sim/warrior/revenge.go index 0ad0568547..35fbd6412a 100644 --- a/sim/warrior/revenge.go +++ b/sim/warrior/revenge.go @@ -25,7 +25,7 @@ func (warrior *Warrior) RegisterRevengeSpell() { }, }) - extraHit := warrior.Talents.ImprovedRevenge > 0 && warrior.Env.GetNumTargets() > 1 + canExtraHit := (warrior.Talents.ImprovedRevenge > 0) && (warrior.Env.TotalTargetCount() > 1) extraHitMult := 0.5 * float64(warrior.Talents.ImprovedRevenge) warrior.Revenge = warrior.RegisterSpell(core.SpellConfig{ @@ -67,8 +67,8 @@ func (warrior *Warrior) RegisterRevengeSpell() { spell.IssueRefund(sim) } - if extraHit { - otherTarget := sim.Environment.NextTargetUnit(target) + if canExtraHit && (sim.Environment.ActiveTargetCount() > 1) { + otherTarget := sim.Environment.NextActiveTargetUnit(target) // TODO: Reimplement using scaling coefficients and variance once those stats are available baseDamage := sim.Roll(1618.3, 1977.92) + ap spell.CalcAndDealDamage(sim, otherTarget, baseDamage*extraHitMult, spell.OutcomeMeleeSpecialHitAndCrit) diff --git a/sim/warrior/sunder_armor.go b/sim/warrior/sunder_armor.go index 3eb23154b1..2741ae7cb2 100644 --- a/sim/warrior/sunder_armor.go +++ b/sim/warrior/sunder_armor.go @@ -9,7 +9,6 @@ func (warrior *Warrior) RegisterSunderArmor() *core.Spell { warrior.SunderArmorAuras = warrior.NewEnemyAuraArray(core.SunderArmorAura) hasGlyph := warrior.HasMajorGlyph(proto.WarriorMajorGlyph_GlyphOfSunderArmor) - numTargets := warrior.Env.GetNumTargets() config := core.SpellConfig{ ActionID: core.ActionID{SpellID: 7386}, SpellSchool: core.SpellSchoolPhysical, @@ -45,8 +44,8 @@ func (warrior *Warrior) RegisterSunderArmor() *core.Spell { if result.Landed() { warrior.TryApplySunderArmorEffect(sim, target) // https://www.wowhead.com/cata/item=43427/glyph-of-sunder-armor - also applies to devastate in cata - if hasGlyph && numTargets > 1 { - nextTarget := warrior.Env.NextTargetUnit(target) + if hasGlyph && (warrior.Env.ActiveTargetCount() > 1) { + nextTarget := warrior.Env.NextActiveTargetUnit(target) warrior.TryApplySunderArmorEffect(sim, nextTarget) } } else { diff --git a/sim/warrior/talents.go b/sim/warrior/talents.go index db7046dfc0..7a5baabb90 100644 --- a/sim/warrior/talents.go +++ b/sim/warrior/talents.go @@ -249,7 +249,7 @@ func (warrior *Warrior) applyBloodAndThunder() { }, Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { // B&T resnapshots all of the rends it applies and will overwrite "better" rends on any target the TC hits - for _, target := range sim.Encounter.TargetUnits { + for _, target := range sim.Encounter.ActiveTargetUnits { rend := warrior.Rend.Dot(target) lastAppliedTime = int64(sim.CurrentTime) diff --git a/sim/warrior/thunder_clap.go b/sim/warrior/thunder_clap.go index 22f7fb898d..00838f7df8 100644 --- a/sim/warrior/thunder_clap.go +++ b/sim/warrior/thunder_clap.go @@ -43,7 +43,7 @@ func (warrior *Warrior) RegisterThunderClapSpell() { baseDamage := 303.0 + 0.228*spell.MeleeAttackPower() baseDamage *= sim.Encounter.AOECapMultiplier() - for _, aoeTarget := range sim.Encounter.TargetUnits { + for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMeleeSpecialNoBlockDodgeParry) if result.Landed() { warrior.ThunderClapAuras.Get(aoeTarget).Activate(sim) diff --git a/sim/warrior/whirlwind.go b/sim/warrior/whirlwind.go index 4f6a1f29b9..a086301839 100644 --- a/sim/warrior/whirlwind.go +++ b/sim/warrior/whirlwind.go @@ -9,8 +9,8 @@ import ( func (warrior *Warrior) RegisterWhirlwindSpell() { actionID := core.ActionID{SpellID: 1680} - numHits := warrior.Env.GetNumTargets() // Whirlwind is uncapped in Cata - results := make([]*core.SpellResult, numHits) + maxHits := warrior.Env.TotalTargetCount() // Whirlwind is uncapped in Cata + results := make([]*core.SpellResult, maxHits) var whirlwindOH *core.Spell if warrior.AutoAttacks.IsDualWielding && warrior.GetOHWeapon().WeaponType != proto.WeaponType_WeaponTypeStaff && @@ -30,17 +30,16 @@ func (warrior *Warrior) RegisterWhirlwindSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { curTarget := target + numHits := sim.Environment.ActiveTargetCount() for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { baseDamage := 0.65 * spell.Unit.OHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) results[hitIndex] = whirlwindOH.CalcDamage(sim, curTarget, baseDamage, whirlwindOH.OutcomeMeleeWeaponSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } - curTarget = target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { whirlwindOH.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) } }, }) @@ -78,6 +77,7 @@ func (warrior *Warrior) RegisterWhirlwindSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { curTarget := target + numHits := sim.Environment.ActiveTargetCount() numLandedHits := 0 for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { baseDamage := 0.65 * spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) @@ -86,13 +86,11 @@ func (warrior *Warrior) RegisterWhirlwindSpell() { numLandedHits++ } - curTarget = sim.Environment.NextTargetUnit(curTarget) + curTarget = sim.Environment.NextActiveTargetUnit(curTarget) } - curTarget = target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { spell.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) } if numLandedHits >= 4 {