Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sim/core/apl.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) {
return
}

if apl.unit.IsChanneling() && !apl.unit.ChanneledDot.Spell.Flags.Matches(SpellFlagCastWhileChanneling) {
if apl.unit.IsChanneling() {
return
}

Expand Down
6 changes: 3 additions & 3 deletions sim/core/armor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ func (result *SpellResult) applyArmor(spell *Spell, isPeriodic bool, attackTable

result.Damage *= armorMitigationMultiplier

result.ArmorMultiplier = armorMitigationMultiplier
result.PostArmorDamage = result.Damage
result.ArmorAndResistanceMultiplier = armorMitigationMultiplier
result.PostArmorAndResistanceMultiplier = result.Damage
}

// Returns Armor mitigation fraction for the spell
func (spell *Spell) armorMultiplier(isPeriodic bool, attackTable *AttackTable) float64 {
if spell.Flags.Matches(SpellFlagIgnoreArmor) {
if spell.Flags.Matches(SpellFlagIgnoreResists) {
return 1
}

Expand Down
4 changes: 2 additions & 2 deletions sim/core/attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ func (unit *Unit) EnableAutoAttacks(agent Agent, options AutoAttackOptions) {
ActionID: ActionID{OtherID: proto.OtherAction_OtherActionAttack, Tag: 2},
SpellSchool: options.OffHand.GetSpellSchool(),
ProcMask: Ternary(options.ProcMask == ProcMaskUnknown, ProcMaskMeleeOHAuto, options.ProcMask),
Flags: SpellFlagMeleeMetrics | SpellFlagNoOnCastComplete,
Flags: SpellFlagMeleeMetrics | SpellFlagIncludeTargetBonusDamage | SpellFlagNoOnCastComplete,

DamageMultiplier: 1,
DamageMultiplierAdditive: 1,
Expand All @@ -463,7 +463,7 @@ func (unit *Unit) EnableAutoAttacks(agent Agent, options AutoAttackOptions) {
ActionID: ActionID{OtherID: proto.OtherAction_OtherActionShoot},
SpellSchool: options.Ranged.GetSpellSchool(),
ProcMask: Ternary(options.ProcMask == ProcMaskUnknown, ProcMaskRangedAuto, options.ProcMask),
Flags: SpellFlagMeleeMetrics | SpellFlagRanged,
Flags: SpellFlagMeleeMetrics | SpellFlagIncludeTargetBonusDamage,
MissileSpeed: 40,

DamageMultiplier: 1,
Expand Down
2 changes: 1 addition & 1 deletion sim/core/aura_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func (unit *Unit) NewDamageAbsorptionAura(config AbsorptionAuraConfig) *DamageAb
})

extraSpellCheck := func(sim *Simulation, spell *Spell, result *SpellResult, isPeriodic bool) bool {
return !spell.Flags.Matches(SpellFlagBypassAbsorbs) && ((config.ShouldApplyToResult == nil) || config.ShouldApplyToResult(sim, spell, result, isPeriodic))
return ((config.ShouldApplyToResult == nil) || config.ShouldApplyToResult(sim, spell, result, isPeriodic))
}

unit.AddDynamicDamageTakenModifier(func(sim *Simulation, spell *Spell, result *SpellResult, isPeriodic bool) {
Expand Down
2 changes: 1 addition & 1 deletion sim/core/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc {
return spell.castFailureHelper(sim, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime)
}

if spell.Unit.IsCastingDuringChannel() && !spell.CanCastDuringChannel(sim) {
if !spell.CanCastDuringChannel(sim) {
return spell.castFailureHelper(sim, "cannot interrupt in-progress channel of %v with a cast of %v", spell.Unit.ChanneledDot.ActionID, spell.ActionID)
}

Expand Down
162 changes: 81 additions & 81 deletions sim/core/consumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,86 +323,86 @@ func registerExplosivesCD(agent Agent, consumes *proto.ConsumesSpec) {
return
}
switch consumes.ExplosiveId {
case 89637:
bomb := character.GetOrRegisterSpell(SpellConfig{
ActionID: BigDaddyActionID,
SpellSchool: SpellSchoolFire,
ProcMask: ProcMaskEmpty,
Flags: SpellFlagAoE,

Cast: CastConfig{
CD: Cooldown{
Timer: character.NewTimer(),
Duration: time.Minute,
},

DefaultCast: Cast{
CastTime: time.Millisecond * 500,
},

ModifyCast: func(sim *Simulation, spell *Spell, cast *Cast) {
spell.Unit.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime)
spell.Unit.AutoAttacks.StopRangedUntil(sim, sim.CurrentTime)
},
},

// Explosives always have 1% resist chance, so just give them hit cap.
BonusHitPercent: 100,
DamageMultiplier: 1,
CritMultiplier: 2,
ThreatMultiplier: 1,

ApplyEffects: func(sim *Simulation, _ *Unit, spell *Spell) {
spell.CalcAndDealAoeDamage(sim, 5006, spell.OutcomeMagicHitAndCrit)
},
})

character.AddMajorCooldown(MajorCooldown{
Spell: bomb,
Type: CooldownTypeDPS | CooldownTypeExplosive,
Priority: CooldownPriorityLow + 10,
})
case 40771:
boltGun := character.GetOrRegisterSpell(SpellConfig{
ActionID: ActionID{SpellID: 82207},
SpellSchool: SpellSchoolFire,
ProcMask: ProcMaskEmpty,
Flags: SpellFlagNoOnCastComplete | SpellFlagCanCastWhileMoving,

Cast: CastConfig{
DefaultCast: Cast{
GCD: GCDDefault,
CastTime: time.Second,
},
IgnoreHaste: true,
CD: Cooldown{
Timer: character.NewTimer(),
Duration: time.Minute * 2,
},
SharedCD: Cooldown{
Timer: character.GetOffensiveTrinketCD(),
Duration: time.Second * 15,
},
},

// Explosives always have 1% resist chance, so just give them hit cap.
BonusHitPercent: 100,
DamageMultiplier: 1,
CritMultiplier: 2,
ThreatMultiplier: 1,

ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) {
spell.CalcAndDealDamage(sim, target, 8860, spell.OutcomeMagicHitAndCrit)
},
})

character.AddMajorCooldown(MajorCooldown{
Spell: boltGun,
Type: CooldownTypeDPS | CooldownTypeExplosive,
Priority: CooldownPriorityLow + 10,
ShouldActivate: func(s *Simulation, c *Character) bool {
return false // Intentionally not automatically used
},
})
// case 89637:
// bomb := character.GetOrRegisterSpell(SpellConfig{
// ActionID: BigDaddyActionID,
// SpellSchool: SpellSchoolFire,
// ProcMask: ProcMaskEmpty,
// Flags: SpellFlagAoE,

// Cast: CastConfig{
// CD: Cooldown{
// Timer: character.NewTimer(),
// Duration: time.Minute,
// },

// DefaultCast: Cast{
// CastTime: time.Millisecond * 500,
// },

// ModifyCast: func(sim *Simulation, spell *Spell, cast *Cast) {
// spell.Unit.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime)
// spell.Unit.AutoAttacks.StopRangedUntil(sim, sim.CurrentTime)
// },
// },

// // Explosives always have 1% resist chance, so just give them hit cap.
// BonusHitPercent: 100,
// DamageMultiplier: 1,
// CritMultiplier: 2,
// ThreatMultiplier: 1,

// ApplyEffects: func(sim *Simulation, _ *Unit, spell *Spell) {
// spell.CalcAndDealAoeDamage(sim, 5006, spell.OutcomeMagicHitAndCrit)
// },
// })

// character.AddMajorCooldown(MajorCooldown{
// Spell: bomb,
// Type: CooldownTypeDPS | CooldownTypeExplosive,
// Priority: CooldownPriorityLow + 10,
// })
// case 40771:
// boltGun := character.GetOrRegisterSpell(SpellConfig{
// ActionID: ActionID{SpellID: 82207},
// SpellSchool: SpellSchoolFire,
// ProcMask: ProcMaskEmpty,
// Flags: SpellFlagNoOnCastComplete | SpellFlagCanCastWhileMoving,

// Cast: CastConfig{
// DefaultCast: Cast{
// GCD: GCDDefault,
// CastTime: time.Second,
// },
// IgnoreHaste: true,
// CD: Cooldown{
// Timer: character.NewTimer(),
// Duration: time.Minute * 2,
// },
// SharedCD: Cooldown{
// Timer: character.GetOffensiveTrinketCD(),
// Duration: time.Second * 15,
// },
// },

// // Explosives always have 1% resist chance, so just give them hit cap.
// BonusHitPercent: 100,
// DamageMultiplier: 1,
// CritMultiplier: 2,
// ThreatMultiplier: 1,

// ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) {
// spell.CalcAndDealDamage(sim, target, 8860, spell.OutcomeMagicHitAndCrit)
// },
// })

// character.AddMajorCooldown(MajorCooldown{
// Spell: boltGun,
// Type: CooldownTypeDPS | CooldownTypeExplosive,
// Priority: CooldownPriorityLow + 10,
// ShouldActivate: func(s *Simulation, c *Character) bool {
// return false // Intentionally not automatically used
// },
// })
}
}
2 changes: 1 addition & 1 deletion sim/core/dot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewFakeElementalShaman(char *Character, _ *proto.Player) Agent {
ActionID: ActionID{SpellID: 42},
SpellSchool: SpellSchoolShadow,
ProcMask: ProcMaskSpellDamage,
Flags: SpellFlagIgnoreArmor,
Flags: SpellFlagIgnoreResists,
Cast: CastConfig{},

BonusCritPercent: 3,
Expand Down
105 changes: 71 additions & 34 deletions sim/core/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"github.com/wowsims/tbc/sim/core/proto"
"github.com/wowsims/tbc/sim/core/stats"
)

//go:generate stringer -type=ProcMask
Expand Down Expand Up @@ -114,10 +115,16 @@ const (
// These bits are set by the crit and damage rolls.
OutcomeCrit
OutcomeCrush

OutcomePartial1
OutcomePartial2
OutcomePartial4
OutcomePartial8
)

const (
OutcomeLanded = OutcomeHit | OutcomeCrit | OutcomeCrush | OutcomeGlance | OutcomeBlock
OutcomePartial = OutcomePartial1 | OutcomePartial2 | OutcomePartial4 | OutcomePartial8
OutcomeLanded = OutcomeHit | OutcomeCrit | OutcomeCrush | OutcomeGlance | OutcomeBlock
)

func (ho HitOutcome) String() string {
Expand Down Expand Up @@ -148,6 +155,18 @@ func (ho HitOutcome) String() string {
}
}

func (ho HitOutcome) PartialResistString() string {
if ho.Matches(OutcomePartial1) {
return " (30% Resist)"
} else if ho.Matches(OutcomePartial2) {
return " (20% Resist)"
} else if ho.Matches(OutcomePartial4) {
return " (10% Resist)"
} else {
return ""
}
}

// Other flags
type SpellFlag uint64

Expand All @@ -157,39 +176,36 @@ func (se SpellFlag) Matches(other SpellFlag) bool {
}

const (
SpellFlagNone SpellFlag = 0
SpellFlagIgnoreArmor SpellFlag = 1 << iota // skip armor
SpellFlagIgnoreTargetModifiers // skip target damage modifiers
SpellFlagIgnoreAttackerModifiers // skip attacker damage modifiers
SpellFlagApplyArmorReduction // Forces damage reduction from armor to apply, even if it otherwise wouldn't.
SpellFlagCannotBeDodged // Ignores dodge in physical hit rolls
SpellFlagBinary // Does not do partial resists and could need a different hit roll.
SpellFlagBypassAbsorbs // Prevents any active DamageAbsorptionAuras from applying their damage reduction effects.
SpellFlagChanneled // Spell is channeled
SpellFlagCastWhileChanneling // Spell can be cast while channeling. If SpellFlagChanneled and SpellFlagCastWhileChanneling are both set, it means that other spells with the SpellFlagCastWhileChanneling flag can be cast without interrupting the channeled spell.
SpellFlagDisease // Spell is categorized as disease
SpellFlagHelpful // For healing spells / buffs.
SpellFlagMeleeMetrics // Marks a spell as a melee ability for metrics.
SpellFlagNoOnCastComplete // Disables the OnCastComplete callback.
SpellFlagNoMetrics // Disables metrics for a spell.
SpellFlagNoLogs // Disables logs for a spell.
SpellFlagAPL // Indicates this spell can be used from an APL rotation.
SpellFlagMCD // Indicates this spell is a MajorCooldown.
SpellFlagReactive // Allows a spell flagged as an MCD to be cast off-GCD. Used for instant cast defensive CDs.
SpellFlagNoOnDamageDealt // Disables OnSpellHitDealt and OnPeriodicDamageDealt aura callbacks for this spell.
SpellFlagPrepullOnly // Indicates this spell should only be used during prepull. Not enforced, just a signal for the APL UI.
SpellFlagEncounterOnly // Indicates this spell should only be used during the encounter (not prepull). Not enforced, just a signal for the APL UI.
SpellFlagPotion // Indicates this spell is a potion spell.
SpellFlagPrepullPotion // Indicates this spell is the prepull potion.
SpellFlagCombatPotion // Indicates this spell is the combat potion.
SpellFlagNoSpellMods // Indicates that no spell mods should be applied to this spell
SpellFlagCanCastWhileMoving // Allows the cast to be casted while moving
SpellFlagPassiveSpell // Indicates this spell is applied/cast as a result of another spell
SpellFlagSupressDoTApply // If present this spell will not apply dots (Used for DTR dot supression)
SpellFlagSwapped // Indicates that this spell is not useable because it is from a currently swapped item
SpellFlagAoE // Indicates that this spell is an AoE spell. Spells flagged with this will use the AoE Cap multiplier when calculating damage.
SpellFlagRanged // Indicates that this spell is a ranged spell. Spells flagged with this will have increased damage when Hunters Mark is active.
SpellFlagReadinessTrinket // Indicates that this spell part of Readiness. Used by Siege of Orgrimmar CDR trinkets.
SpellFlagNone SpellFlag = 0
SpellFlagIgnoreResists SpellFlag = 1 << iota // skip spell resist/armor
SpellFlagIgnoreTargetModifiers // skip target damage modifiers
SpellFlagIgnoreAttackerModifiers // skip attacker damage modifiers
SpellFlagApplyArmorReduction // Forces damage reduction from armor to apply, even if it otherwise wouldn't.
SpellFlagCannotBeDodged // Ignores dodge in physical hit rolls
SpellFlagIncludeTargetBonusDamage // Spell benefits from Gift of Arthas and Hemorrhage.
SpellFlagBinary // Does not do partial resists and could need a different hit roll.
SpellFlagChanneled // Spell is channeled
SpellFlagDisease // Spell is categorized as disease
SpellFlagHauntSE // Spell benefits from haunt/SE effects
SpellFlagHelpful // For healing spells / buffs.
SpellFlagMeleeMetrics // Marks a spell as a melee ability for metrics.
SpellFlagNoOnCastComplete // Disables the OnCastComplete callback.
SpellFlagNoMetrics // Disables metrics for a spell.
SpellFlagNoLogs // Disables logs for a spell.
SpellFlagAPL // Indicates this spell can be used from an APL rotation.
SpellFlagMCD // Indicates this spell is a MajorCooldown.
SpellFlagReactive // Allows a spell flagged as an MCD to be cast off-GCD. Used for instant cast defensive CDs.
SpellFlagNoOnDamageDealt // Disables OnSpellHitDealt and OnPeriodicDamageDealt aura callbacks for this spell.
SpellFlagPrepullOnly // Indicates this spell should only be used during prepull. Not enforced, just a signal for the APL UI.
SpellFlagEncounterOnly // Indicates this spell should only be used during the encounter (not prepull). Not enforced, just a signal for the APL UI.
SpellFlagPotion // Indicates this spell is a potion spell.
SpellFlagPrepullPotion // Indicates this spell is the prepull potion.
SpellFlagCombatPotion // Indicates this spell is the combat potion.
SpellFlagNoSpellMods // Indicates that no spell mods should be applied to this spell
SpellFlagCanCastWhileMoving // Allows the cast to be casted while moving
SpellFlagPassiveSpell // Indicates this spell is applied/cast as a result of another spell
SpellFlagSupressDoTApply // If present this spell will not apply dots (Used for DTR dot supression)
SpellFlagSwapped // Indicates that this spell is not useable because it is from a currently swapped item

// Used to let agents categorize their spells.
SpellFlagAgentReserved1
Expand Down Expand Up @@ -225,6 +241,27 @@ func (ss SpellSchool) Matches(other SpellSchool) bool {
return (ss & other) != 0
}

func (ss SpellSchool) ResistanceStat() stats.Stat {
switch ss {
case SpellSchoolPhysical:
return stats.ArmorPenetration
case SpellSchoolArcane:
return stats.ArcaneResistance
case SpellSchoolFire:
return stats.FireResistance
case SpellSchoolFrost:
return stats.FrostResistance
case SpellSchoolHoly:
return 0 // Holy resistance doesn't exist.
case SpellSchoolNature:
return stats.NatureResistance
case SpellSchoolShadow:
return stats.ShadowResistance
default:
return 0 // This applies to spell school combinations, which supposedly use the "path of the least resistance", so 0 is a good fit.
}
}

func SpellSchoolFromProto(p proto.SpellSchool) SpellSchool {
switch p {
case proto.SpellSchool_SpellSchoolPhysical:
Expand Down
Loading
Loading