Skip to content

Commit

Permalink
Merge pull request #4103 from wowsims/apl
Browse files Browse the repository at this point in the history
Implement item swapping in APL
jimmyt857 authored Dec 23, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents afdf72a + dd20c99 commit 4ce80f8
Showing 31 changed files with 407 additions and 389 deletions.
3 changes: 3 additions & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
@@ -27,6 +27,9 @@ message Player {
Consumes consumes = 4;
UnitStats bonus_stats = 36;

bool enable_item_swap = 46;
ItemSwap item_swap = 45;

IndividualBuffs buffs = 15;

oneof spec {
14 changes: 13 additions & 1 deletion proto/apl.proto
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ message APLListItem {
APLAction action = 3; // The action to be performed.
}

// NextIndex: 17
// NextIndex: 18
message APLAction {
APLValue condition = 1; // If set, action will only execute if value is true or != 0.

@@ -73,6 +73,7 @@ message APLAction {
APLActionActivateAura activate_aura = 13;
APLActionCancelAura cancel_aura = 10;
APLActionTriggerICD trigger_icd = 11;
APLActionItemSwap item_swap = 17;
}
}

@@ -249,6 +250,17 @@ message APLActionTriggerICD {
ActionID aura_id = 1;
}

message APLActionItemSwap {
enum SwapSet {
Unknown = 0;
Main = 1;
Swap1 = 2;
}

// The set to swap to.
SwapSet swap_set = 1;
}

///////////////////////////////////////////////////////////////////////////
// VALUES
///////////////////////////////////////////////////////////////////////////
2 changes: 2 additions & 0 deletions proto/ui.proto
Original file line number Diff line number Diff line change
@@ -293,6 +293,8 @@ message SavedSettings {
Cooldowns cooldowns = 6;
string rotation_json = 8;
repeated Profession professions = 9;
bool enable_item_swap = 18;
ItemSwap item_swap = 17;

int32 reaction_time_ms = 10;
int32 channel_clip_delay_ms = 14;
2 changes: 2 additions & 0 deletions sim/core/apl_action.go
Original file line number Diff line number Diff line change
@@ -175,6 +175,8 @@ func (rot *APLRotation) newAPLActionImpl(config *proto.APLAction) APLActionImpl
return rot.newActionCancelAura(config.GetCancelAura())
case *proto.APLAction_TriggerIcd:
return rot.newActionTriggerICD(config.GetTriggerIcd())
case *proto.APLAction_ItemSwap:
return rot.newActionItemSwap(config.GetItemSwap())
default:
return nil
}
44 changes: 44 additions & 0 deletions sim/core/apl_actions_misc.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package core

import (
"fmt"

"github.com/wowsims/wotlk/sim/core/proto"
)

@@ -117,3 +118,46 @@ func (action *APLActionTriggerICD) Execute(sim *Simulation) {
func (action *APLActionTriggerICD) String() string {
return fmt.Sprintf("Trigger ICD(%s)", action.aura.ActionID)
}

type APLActionItemSwap struct {
defaultAPLActionImpl
character *Character
swapSet proto.APLActionItemSwap_SwapSet
}

func (rot *APLRotation) newActionItemSwap(config *proto.APLActionItemSwap) APLActionImpl {
if config.SwapSet == proto.APLActionItemSwap_Unknown {
rot.ValidationWarning("Unknown item swap set")
return nil
}

character := rot.unit.Env.Raid.GetPlayerFromUnit(rot.unit).GetCharacter()
if !character.ItemSwap.IsEnabled() {
if config.SwapSet != proto.APLActionItemSwap_Main {
rot.ValidationWarning("No swap set configured in Settings.")
}
return nil
}

return &APLActionItemSwap{
character: character,
swapSet: config.SwapSet,
}
}
func (action *APLActionItemSwap) IsReady(sim *Simulation) bool {
return (action.swapSet == proto.APLActionItemSwap_Main) == action.character.ItemSwap.IsSwapped()
}
func (action *APLActionItemSwap) Execute(sim *Simulation) {
if sim.Log != nil {
action.character.Log(sim, "Item Swap to set %s", action.swapSet)
}

if action.swapSet == proto.APLActionItemSwap_Main {
action.character.ItemSwap.reset(sim)
} else {
action.character.ItemSwap.SwapItems(sim, action.character.ItemSwap.slots, true)
}
}
func (action *APLActionItemSwap) String() string {
return fmt.Sprintf("Item Swap(%s)", action.swapSet)
}
4 changes: 4 additions & 0 deletions sim/core/character.go
Original file line number Diff line number Diff line change
@@ -178,6 +178,10 @@ func NewCharacter(party *Party, partyIndex int, player *proto.Player) Character
}
character.PseudoStats.InFrontOfTarget = player.InFrontOfTarget

if player.EnableItemSwap && player.ItemSwap != nil {
character.enableItemSwap(player.ItemSwap, character.DefaultMeleeCritMultiplier(), character.DefaultMeleeCritMultiplier(), 0)
}

return character
}

60 changes: 44 additions & 16 deletions sim/core/item_swaps.go
Original file line number Diff line number Diff line change
@@ -19,6 +19,9 @@ type ItemSwap struct {
ohCritMultiplier float64
rangedCritMultiplier float64

// Which slots to actually swap.
slots []proto.ItemSlot

// Used for resetting
initialEquippedItems [3]Item
initialUnequippedItems [3]Item
@@ -33,15 +36,49 @@ TODO All the extra parameters here and the code in multiple places for handling
we'll need to figure out something cleaner as this will be quite error-prone
*/
func (character *Character) EnableItemSwap(itemSwap *proto.ItemSwap, mhCritMultiplier float64, ohCritMultiplier float64, rangedCritMultiplier float64) {
items := getItems(itemSwap)
func (character *Character) enableItemSwap(itemSwap *proto.ItemSwap, mhCritMultiplier float64, ohCritMultiplier float64, rangedCritMultiplier float64) {
var slots []proto.ItemSlot
hasMhSwap := itemSwap.MhItem != nil && itemSwap.MhItem.Id != 0
hasOhSwap := itemSwap.OhItem != nil && itemSwap.OhItem.Id != 0
hasRangedSwap := itemSwap.RangedItem != nil && itemSwap.RangedItem.Id != 0

mainItems := [3]Item{
character.Equipment[proto.ItemSlot_ItemSlotMainHand],
character.Equipment[proto.ItemSlot_ItemSlotOffHand],
character.Equipment[proto.ItemSlot_ItemSlotRanged],
}
swapItems := [3]Item{
toItem(itemSwap.MhItem),
toItem(itemSwap.OhItem),
toItem(itemSwap.RangedItem),
}

// Handle MH and OH together, because present MH + empty OH --> swap MH and unequip OH
if hasMhSwap || hasOhSwap {
if swapItems[0].ID != mainItems[0].ID {
slots = append(slots, proto.ItemSlot_ItemSlotMainHand)
}
if swapItems[1].ID != mainItems[1].ID {
slots = append(slots, proto.ItemSlot_ItemSlotOffHand)
}
}
if hasRangedSwap {
if swapItems[2].ID != mainItems[2].ID {
slots = append(slots, proto.ItemSlot_ItemSlotRanged)
}
}

if len(slots) == 0 {
return
}

character.ItemSwap = ItemSwap{
character: character,
mhCritMultiplier: mhCritMultiplier,
ohCritMultiplier: ohCritMultiplier,
rangedCritMultiplier: rangedCritMultiplier,
unEquippedItems: items,
slots: slots,
unEquippedItems: swapItems,
swapped: false,
}
}
@@ -161,7 +198,10 @@ func (swap *ItemSwap) SwapItems(sim *Simulation, slots []proto.ItemSlot, useGCD
}

if useGCD {
character.SetGCDTimer(sim, 1500*time.Millisecond+sim.CurrentTime)
newGCD := sim.CurrentTime + 1500*time.Millisecond
if newGCD > character.GCD.ReadyAt() {
character.SetGCDTimer(sim, newGCD)
}
}
swap.swapped = !swap.swapped
}
@@ -261,18 +301,6 @@ func getInitialEquippedItems(character *Character) [3]Item {
return items
}

func getItems(itemSwap *proto.ItemSwap) [3]Item {
var items [3]Item

if itemSwap != nil {
items[0] = toItem(itemSwap.MhItem)
items[1] = toItem(itemSwap.OhItem)
items[2] = toItem(itemSwap.RangedItem)
}

return items
}

func toItem(itemSpec *proto.ItemSpec) Item {
if itemSpec == nil {
return Item{}
4 changes: 0 additions & 4 deletions sim/deathknight/dps/dps_deathknight.go
Original file line number Diff line number Diff line change
@@ -75,10 +75,6 @@ func NewDpsDeathknight(character *core.Character, player *proto.Player) *DpsDeat
AutoSwingMelee: true,
})

if dpsDk.Talents.SummonGargoyle && dpsDk.Rotation.UseGargoyle && dpsDk.Rotation.EnableWeaponSwap {
dpsDk.EnableItemSwap(dpsDk.Rotation.WeaponSwap, dpsDk.DefaultMeleeCritMultiplier(), dpsDk.DefaultMeleeCritMultiplier(), 0)
}

dpsDk.br.dk = dpsDk
dpsDk.sr.dk = dpsDk
dpsDk.ur.dk = dpsDk
13 changes: 6 additions & 7 deletions sim/shaman/enhancement/enhancement.go
Original file line number Diff line number Diff line change
@@ -62,10 +62,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh

enh.ApplySyncType(enhOptions.Options.SyncType)

if enh.Totems.UseFireElemental && enhOptions.Rotation.EnableItemSwap {
enh.EnableItemSwap(enhOptions.Rotation.ItemSwap, enh.DefaultMeleeCritMultiplier(), enh.DefaultMeleeCritMultiplier(), 0)
}

if enhOptions.Rotation.LightningboltWeave {
enh.maelstromWeaponMinStack = enhOptions.Rotation.MaelstromweaponMinStack
} else {
@@ -136,9 +132,12 @@ func (enh *EnhancementShaman) Initialize() {
})
}
enh.DelayDPSCooldowns(3 * time.Second)
enh.RegisterPrepullAction(-time.Second, func(sim *core.Simulation) {
enh.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, false)
})

if !enh.IsUsingAPL {
enh.RegisterPrepullAction(-time.Second, func(sim *core.Simulation) {
enh.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, false)
})
}
}

func (enh *EnhancementShaman) Reset(sim *core.Simulation) {
2 changes: 1 addition & 1 deletion sim/shaman/fire_elemental_totem.go
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ func (shaman *Shaman) registerFireElementalTotem() {
shaman.FireElemental.EnableWithTimeout(sim, shaman.FireElemental, fireTotemDuration)

//TODO handle more then one swap if the fight is greater then 5 mins, for now will just do the one.
if shaman.FireElementalTotem.SpellMetrics[target.Index].Casts == 1 {
if !shaman.IsUsingAPL && shaman.FireElementalTotem.SpellMetrics[target.Index].Casts == 1 {
shaman.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, true)
}

10 changes: 4 additions & 6 deletions sim/warlock/warlock.go
Original file line number Diff line number Diff line change
@@ -203,8 +203,10 @@ func (warlock *Warlock) Reset(sim *core.Simulation) {
warlock.petStmBonusSP = 0
}

warlock.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand,
proto.ItemSlot_ItemSlotOffHand, proto.ItemSlot_ItemSlotRanged}, false)
if !warlock.IsUsingAPL {
warlock.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand,
proto.ItemSlot_ItemSlotOffHand, proto.ItemSlot_ItemSlotRanged}, false)
}
warlock.corrRefreshList = make([]time.Duration, len(warlock.Env.Encounter.TargetUnits))
warlock.setupCooldowns(sim)
}
@@ -236,10 +238,6 @@ func NewWarlock(character *core.Character, options *proto.Player) *Warlock {

warlock.Infernal = warlock.NewInfernal()

if warlock.Rotation.Type == proto.Warlock_Rotation_Affliction && warlock.Rotation.EnableWeaponSwap {
warlock.EnableItemSwap(warlock.Rotation.WeaponSwap, 1, 1, 1)
}

warlock.applyWeaponImbue()
wotlk.ConstructValkyrPets(&warlock.Character)

10 changes: 0 additions & 10 deletions sim/warlock/warlock_test.go
Original file line number Diff line number Diff line change
@@ -22,9 +22,6 @@ func TestAffliction(t *testing.T) {
Glyphs: AfflictionGlyphs,
Consumes: FullConsumes,
SpecOptions: core.SpecOptionsCombo{Label: "Affliction Warlock", SpecOptions: DefaultAfflictionWarlock},
OtherSpecOptions: []core.SpecOptionsCombo{
{Label: "AffItemSwap", SpecOptions: afflictionItemSwap},
},

ItemFilter: ItemFilter,
}))
@@ -129,13 +126,6 @@ var DefaultAfflictionWarlock = &proto.Player_Warlock{
},
}

var afflictionItemSwap = &proto.Player_Warlock{
Warlock: &proto.Warlock{
Options: defaultAfflictionOptions,
Rotation: afflictionItemSwapRotation,
},
}

var defaultAfflictionOptions = &proto.Warlock_Options{
Armor: proto.Warlock_Options_FelArmor,
Summon: proto.Warlock_Options_Felhunter,
Loading

0 comments on commit 4ce80f8

Please sign in to comment.