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

Implement item swapping in APL #4103

Merged
merged 1 commit into from
Dec 23, 2023
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
3 changes: 3 additions & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 13 additions & 1 deletion proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

Expand Down Expand Up @@ -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
///////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 2 additions & 0 deletions proto/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions sim/core/apl_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
44 changes: 44 additions & 0 deletions sim/core/apl_actions_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"fmt"

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

Expand Down Expand Up @@ -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
Expand Up @@ -181,6 +181,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
}

Expand Down
60 changes: 44 additions & 16 deletions sim/core/item_swaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{}
Expand Down
4 changes: 0 additions & 4 deletions sim/deathknight/dps/dps_deathknight.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 6 additions & 7 deletions sim/shaman/enhancement/enhancement.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion sim/shaman/fire_elemental_totem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
10 changes: 4 additions & 6 deletions sim/warlock/warlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)

Expand Down
10 changes: 0 additions & 10 deletions sim/warlock/warlock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}))
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading