Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paladin: Refactor stopattack UI options + add manually triggered attacks as an experimental feature for prot #1110

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 5 additions & 3 deletions proto/paladin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,14 @@ enum PaladinSeal {
message PaladinOptions {
PaladinSeal primarySeal = 1;
PaladinAura aura = 2;
bool IsManuallyTriggeringAutoAttacks = 3;
bool IsUsingDivineStormStopAttack = 4;
bool IsUsingJudgementStopAttack = 5;
bool IsUsingCrusaderStrikeStopAttack = 6;

bool righteousFury = 8;
Blessings personalBlessing = 9;
bool IsUsingHammerOfTheRighteousStopAttack = 7;
bool IsUsingShieldOfRighteousnessStopAttack = 8;
bool righteousFury = 9;
Blessings personalBlessing = 10;
}

message RetributionPaladin {
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/crusader_strike.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (paladin *Paladin) registerCrusaderStrike() {

crusaderStrikeSpell := paladin.RegisterSpell(core.SpellConfig{
ActionID: manaMetrics.ActionID,
SpellCode: SpellCode_PaladinCrusaderStrike,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMelee,
ProcMask: core.ProcMaskMeleeMHSpecial,
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/divine_storm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (paladin *Paladin) registerDivineStorm() {

divineStormSpell := paladin.RegisterSpell(core.SpellConfig{
ActionID: healthMetrics.ActionID,
SpellCode: SpellCode_PaladinDivineStorm,
SpellSchool: core.SpellSchoolPhysical,
DefenseType: core.DefenseTypeMelee,
ProcMask: core.ProcMaskMeleeMHSpecial,
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/hammer_of_the_righteous.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (paladin *Paladin) registerHammerOfTheRighteous() {

paladin.GetOrRegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: int32(proto.PaladinRune_RuneWristHammerOfTheRighteous)},
SpellCode: SpellCode_PaladinHammerOfTheRighteous,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMelee,
ProcMask: core.ProcMaskMeleeMHSpecial,
Expand Down
6 changes: 5 additions & 1 deletion sim/paladin/item_sets_pve.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,11 @@ var ItemSetWilfullJudgement = core.NewItemSet(core.ItemSet{
procChance := 0.2 * float64(paladin.Talents.Reckoning)

handler := func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, actionID, spell.ActionID)
if paladin.Options.IsManuallyTriggeringAutoAttacks {
paladin.AutoAttacks.StoreExtraMHAttack(sim, 1, actionID, spell.ActionID)
} else {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, actionID, spell.ActionID)
}
}

core.MakeProcTriggerAura(&c.Unit, core.ProcTrigger{
Expand Down
56 changes: 42 additions & 14 deletions sim/paladin/paladin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,33 @@ const (
)

const (
SpellCode_PaladinNone = iota

SpellCode_PaladinNone int32 = 0
// Judgements
SpellCode_PaladinJudgementOfCommand = 1 << iota
SpellCode_PaladinJudgementOfMartyrdom
SpellCode_PaladinJudgementOfRighteousness
SpellCode_PaladinJudgementOfTheCrusader
// Special attacks that enable autoattacks/trigger extra attacks
SpellCode_PaladinCrusaderStrike
SpellCode_PaladinDivineStorm
SpellCode_PaladinHammerOfTheRighteous
SpellCode_PaladinShieldOfRighteousness
// Other spells
SpellCode_PaladinExorcism
SpellCode_PaladinHolyShock
SpellCode_PaladinHolyWrath
SpellCode_PaladinJudgementOfCommand
SpellCode_PaladinConsecration
SpellCode_PaladinAvengersShield
SpellCode_PaladinHolyShield
SpellCode_PaladinHolyShieldProc
SpellCode_PaladinLayOnHands
)

const (
SpellCode_PaladinJudgements int32 = SpellCode_PaladinJudgementOfCommand | SpellCode_PaladinJudgementOfMartyrdom | SpellCode_PaladinJudgementOfRighteousness | SpellCode_PaladinJudgementOfTheCrusader
SpellCode_PaladinTriggersExtraAttack = SpellCode_PaladinJudgements | SpellCode_PaladinCrusaderStrike | SpellCode_PaladinDivineStorm | SpellCode_PaladinHammerOfTheRighteous | SpellCode_PaladinShieldOfRighteousness
)

type SealJudgeCode uint8

const (
Expand Down Expand Up @@ -86,6 +100,7 @@ type Paladin struct {
enableMultiJudge bool
lingerDuration time.Duration
consumeSealsOnJudge bool
stopAttackMacroMask int32
}

// Implemented by each Paladin spec.
Expand All @@ -109,6 +124,8 @@ func (paladin *Paladin) AddPartyBuffs(_ *proto.PartyBuffs) {
}

func (paladin *Paladin) Initialize() {
paladin.stopAttackMacroMask = paladin.getStopAttackMacroMask()
paladin.registerStopAttackMacros()
paladin.registerRighteousFury()
// Judgement and Seals
paladin.registerJudgement()
Expand Down Expand Up @@ -149,7 +166,6 @@ func (paladin *Paladin) Initialize() {
paladin.lingerDuration = time.Millisecond * 400
paladin.consumeSealsOnJudge = true

paladin.registerStopAttackMacros()
}

func (paladin *Paladin) Reset(_ *core.Simulation) {
Expand Down Expand Up @@ -202,22 +218,34 @@ func (paladin *Paladin) ResetPrimarySeal(primarySeal proto.PaladinSeal) {
}

func (paladin *Paladin) registerStopAttackMacros() {
paladin.OnSpellRegistered(func(spell *core.Spell) {
if paladin.stopAttackMacroMask&spell.SpellCode != 0 {
spell.Flags |= core.SpellFlagBatchStopAttackMacro
}
})
}

if paladin.divineStorm != nil && paladin.Options.IsUsingDivineStormStopAttack {
paladin.divineStorm.Flags |= core.SpellFlagBatchStopAttackMacro
func (paladin *Paladin) getStopAttackMacroMask() int32 {
options := paladin.Options
if options.IsManuallyTriggeringAutoAttacks {
return SpellCode_PaladinTriggersExtraAttack
}

if paladin.crusaderStrike != nil && paladin.Options.IsUsingCrusaderStrikeStopAttack {
paladin.crusaderStrike.Flags |= core.SpellFlagBatchStopAttackMacro
bitMask := SpellCode_PaladinNone

spellCodes := []int32{
core.TernaryInt32(options.IsUsingJudgementStopAttack, SpellCode_PaladinJudgements, SpellCode_PaladinNone),
core.TernaryInt32(options.IsUsingCrusaderStrikeStopAttack, SpellCode_PaladinCrusaderStrike, SpellCode_PaladinNone),
core.TernaryInt32(options.IsUsingDivineStormStopAttack, SpellCode_PaladinDivineStorm, SpellCode_PaladinNone),
core.TernaryInt32(options.IsUsingHammerOfTheRighteousStopAttack, SpellCode_PaladinHammerOfTheRighteous, SpellCode_PaladinNone),
core.TernaryInt32(options.IsUsingShieldOfRighteousnessStopAttack, SpellCode_PaladinShieldOfRighteousness, SpellCode_PaladinNone),
}

for _, spellsJoX := range paladin.allJudgeSpells {
for _, v := range spellsJoX {
if v != nil && paladin.Options.IsUsingJudgementStopAttack {
v.Flags |= core.SpellFlagBatchStopAttackMacro
}
}
for _, spellCode := range spellCodes {
bitMask |= spellCode
}

return bitMask
}

func (paladin *Paladin) ResetCurrentPaladinAura() {
Expand Down
16 changes: 3 additions & 13 deletions sim/paladin/protection/protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,8 @@ func NewProtectionPaladin(character *core.Character, options *proto.Player) *Pro
pal := paladin.NewPaladin(character, options, protOptions)

prot := &ProtectionPaladin{
Paladin: pal,
primarySeal: protOptions.PrimarySeal,
righteousFury: protOptions.RighteousFury,
IsUsingDivineStormStopAttack: protOptions.IsUsingDivineStormStopAttack,
IsUsingJudgementStopAttack: protOptions.IsUsingJudgementStopAttack,
IsUsingCrusaderStrikeStopAttack: protOptions.IsUsingCrusaderStrikeStopAttack,
personalBlessing: protOptions.PersonalBlessing,
Comment on lines -34 to -38
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to duplicate these spec options into the Protection/Retribution structs. They're all already in the regular Options field of the paladin struct. I left primarySeal just in case, but removed the affected fields here and in the RetributionPaladin struct.

Paladin: pal,
primarySeal: protOptions.PrimarySeal,
}

prot.EnableAutoAttacks(prot, core.AutoAttackOptions{
Expand All @@ -49,12 +44,7 @@ func NewProtectionPaladin(character *core.Character, options *proto.Player) *Pro
type ProtectionPaladin struct {
*paladin.Paladin

primarySeal proto.PaladinSeal
righteousFury bool
IsUsingDivineStormStopAttack bool
IsUsingJudgementStopAttack bool
IsUsingCrusaderStrikeStopAttack bool
personalBlessing proto.Blessings
primarySeal proto.PaladinSeal
}

func (prot *ProtectionPaladin) GetPaladin() *paladin.Paladin {
Expand Down
12 changes: 3 additions & 9 deletions sim/paladin/retribution/retribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ func NewRetributionPaladin(character *core.Character, options *proto.Player) *Re
pal := paladin.NewPaladin(character, options, retOptions)

ret := &RetributionPaladin{
Paladin: pal,
primarySeal: retOptions.PrimarySeal,
IsUsingDivineStormStopAttack: retOptions.IsUsingDivineStormStopAttack,
IsUsingJudgementStopAttack: retOptions.IsUsingJudgementStopAttack,
IsUsingCrusaderStrikeStopAttack: retOptions.IsUsingCrusaderStrikeStopAttack,
Paladin: pal,
primarySeal: retOptions.PrimarySeal,
}

ret.EnableAutoAttacks(ret, core.AutoAttackOptions{
Expand All @@ -47,10 +44,7 @@ func NewRetributionPaladin(character *core.Character, options *proto.Player) *Re
type RetributionPaladin struct {
*paladin.Paladin

primarySeal proto.PaladinSeal
IsUsingDivineStormStopAttack bool
IsUsingJudgementStopAttack bool
IsUsingCrusaderStrikeStopAttack bool
primarySeal proto.PaladinSeal
}

func (ret *RetributionPaladin) GetPaladin() *paladin.Paladin {
Expand Down
6 changes: 5 additions & 1 deletion sim/paladin/runes.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,11 @@ func (paladin *Paladin) registerAegis() {
Outcome: core.OutcomeLanded,
ProcChance: procChance,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, procID, spell.ActionID)
if paladin.Options.IsManuallyTriggeringAutoAttacks {
paladin.AutoAttacks.StoreExtraMHAttack(sim, 1, procID, spell.ActionID)
} else {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, procID, spell.ActionID)
}
},
})
}
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/shield_of_righteousness.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (paladin *Paladin) registerShieldOfRighteousness() {

paladin.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: int32(proto.PaladinRune_RuneCloakShieldOfRighteousness)},
SpellCode: SpellCode_PaladinShieldOfRighteousness,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMelee,
ProcMask: core.ProcMaskMeleeMHSpecial,
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/som.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (paladin *Paladin) registerSealOfMartyrdom() {

judgeSpell := paladin.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: 407803},
SpellCode: SpellCode_PaladinJudgementOfMartyrdom,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMelee,
ProcMask: core.ProcMaskMeleeMHSpecial,
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/sor.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (paladin *Paladin) registerSealOfRighteousness() {

judgeSpell := paladin.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: rank.judge.spellID},
SpellCode: SpellCode_PaladinJudgementOfRighteousness,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMagic,
ProcMask: core.ProcMaskSpellDamage,
Expand Down
1 change: 1 addition & 0 deletions sim/paladin/sotc.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (paladin *Paladin) registerSealOfTheCrusader() {

judgeSpell := paladin.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: rank.judge.spellID},
SpellCode: SpellCode_PaladinJudgementOfTheCrusader,
SpellSchool: core.SpellSchoolHoly,
DefenseType: core.DefenseTypeMagic,
ProcMask: core.ProcMaskEmpty,
Expand Down
6 changes: 5 additions & 1 deletion sim/paladin/talents.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ func (paladin *Paladin) applyReckoning() {
ProcMask: core.ProcMaskMeleeOrRanged,
ProcChance: procChance,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, procID, spell.ActionID)
if paladin.Options.IsManuallyTriggeringAutoAttacks {
paladin.AutoAttacks.StoreExtraMHAttack(sim, 1, procID, spell.ActionID)
} else {
paladin.AutoAttacks.ExtraMHAttack(sim, 1, procID, spell.ActionID)
}
},
})
}
Expand Down
10 changes: 10 additions & 0 deletions ui/protection_paladin/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export const RighteousFuryToggle = InputHelpers.makeSpecOptionsBooleanIconInput<
changeEmitter: (player: Player<Spec.SpecProtectionPaladin>) => TypedEvent.onAny([player.gearChangeEmitter, player.specOptionsChangeEmitter]),
});

export const ManualAutoAttacksToggle = InputHelpers.makeSpecOptionsBooleanInput<Spec.SpecProtectionPaladin>({
fieldName: 'isManuallyTriggeringAutoAttacks',
label: 'Manually Triggered Autoattacks',
labelTooltip: `\
Simulates combat where autoattacks are manually enabled only when the swing timer is ready and turned off again immediately after the melee swing occurs.
Enabling this feature also assumes that all rotational abilities that initiate autoattacks (e.g. Hammer of the Righteous) are cast with a stopattack/@target macro.`,
showWhen: player => player.sim.getShowExperimental(),
changeEmitter: player => TypedEvent.onAny([player.specOptionsChangeEmitter, player.sim.showExperimentalChangeEmitter]),
});

// The below is used in the custom APL action "Cast Primary Seal".
// Only shows SoC if it's talented.
export const PrimarySealSelection = InputHelpers.makeSpecOptionsEnumIconInput<Spec.SpecProtectionPaladin, PaladinSeal>({
Expand Down
16 changes: 16 additions & 0 deletions ui/protection_paladin/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, {
},
};
},
(simUI: IndividualSimUI<Spec.SpecProtectionPaladin>) => {
return {
updateOn: simUI.player.changeEmitter,
getContent: () => {
if (simUI.player.getSpecOptions().isManuallyTriggeringAutoAttacks == true) {
return `You have enabled Manually Triggered Autoattacks, an experimental feature.
This feature simulates perfect execution of storing extra attacks gained through Rekconing,
as well as Wildstrike/HoJ/etc. This is an advanced technique that will be difficult to execute in practice,
and any DPS gains with this feature enabled should be treated as a best-case scenario that may not be realized in a real raid encounter.`;
} else {
return '';
}
},
};
},
],
// All stats for which EP should be calculated.
epStats: [
Expand Down Expand Up @@ -183,6 +198,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, {
OtherInputs.HpPercentForDefensives,
OtherInputs.InspirationUptime,
OtherInputs.InFrontOfTarget,
ProtectionPaladinInputs.ManualAutoAttacksToggle,
//OtherInputs.DistanceFromTarget,
],
},
Expand Down
22 changes: 11 additions & 11 deletions ui/retribution_paladin/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import { TypedEvent } from '../core/typed_event.js';
// These don't need to be in a separate file but it keeps things cleaner.

export const AuraSelection = InputHelpers.makeSpecOptionsEnumIconInput<Spec.SpecRetributionPaladin, PaladinAura>({
fieldName: 'aura',
values: [
{ value: PaladinAura.NoPaladinAura, tooltip: 'No Aura' },
{ actionId: () => ActionId.fromSpellId(20218), value: PaladinAura.SanctityAura },
//{ actionId: () => ActionId.fromSpellId(10299), value: PaladinAura.DevotionAura },
//{ actionId: () => ActionId.fromSpellId(10299), value: PaladinAura.RetributionAura },
//{ actionId: () => ActionId.fromSpellId(19746), value: PaladinAura.ConcentrationAura },
//{ actionId: () => ActionId.fromSpellId(19888), value: PaladinAura.FrostResistanceAura },
//{ actionId: () => ActionId.fromSpellId(19892), value: PaladinAura.ShadowResistanceAura },
//{ actionId: () => ActionId.fromSpellId(19891), value: PaladinAura.FireResistanceAura },
],
fieldName: 'aura',
values: [
{ value: PaladinAura.NoPaladinAura, tooltip: 'No Aura' },
{ actionId: () => ActionId.fromSpellId(20218), value: PaladinAura.SanctityAura },
//{ actionId: () => ActionId.fromSpellId(10299), value: PaladinAura.DevotionAura },
//{ actionId: () => ActionId.fromSpellId(10299), value: PaladinAura.RetributionAura },
//{ actionId: () => ActionId.fromSpellId(19746), value: PaladinAura.ConcentrationAura },
//{ actionId: () => ActionId.fromSpellId(19888), value: PaladinAura.FrostResistanceAura },
//{ actionId: () => ActionId.fromSpellId(19892), value: PaladinAura.ShadowResistanceAura },
//{ actionId: () => ActionId.fromSpellId(19891), value: PaladinAura.FireResistanceAura },
],
});

// The below is used in the custom APL action "Cast Primary Seal".
Expand Down
8 changes: 7 additions & 1 deletion ui/retribution_paladin/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRetributionPaladin, {
excludeBuffDebuffInputs: [],
// Inputs to include in the 'Other' section on the settings tab.
otherInputs: {
inputs: [OtherInputs.TankAssignment, OtherInputs.InFrontOfTarget, RetributionPaladinInputs.CrusaderStrikeStopAttack, RetributionPaladinInputs.JudgementStopAttack, RetributionPaladinInputs.DivineStormStopAttack],
inputs: [
OtherInputs.TankAssignment,
OtherInputs.InFrontOfTarget,
RetributionPaladinInputs.CrusaderStrikeStopAttack,
RetributionPaladinInputs.JudgementStopAttack,
RetributionPaladinInputs.DivineStormStopAttack,
],
},
encounterPicker: {
// Whether to include 'Execute Duration (%)' in the 'Encounter' section of the settings tab.
Expand Down
Loading