From aa036757c009db0e59f533d91dd7332007df610b Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:47:21 -0500 Subject: [PATCH 01/26] update protos for APL only --- proto/apl.proto | 9 ---- proto/common.proto | 7 --- proto/druid.proto | 72 +---------------------------- proto/hunter.proto | 16 ------- proto/mage.proto | 6 --- proto/paladin.proto | 55 ---------------------- proto/priest.proto | 37 --------------- proto/rogue.proto | 65 -------------------------- proto/shaman.proto | 108 -------------------------------------------- proto/ui.proto | 8 ---- proto/warlock.proto | 2 - proto/warrior.proto | 105 ------------------------------------------ 12 files changed, 1 insertion(+), 489 deletions(-) diff --git a/proto/apl.proto b/proto/apl.proto index 0d53eea9d4..cd7bc6dd33 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -9,8 +9,6 @@ import "shaman.proto"; // Rotation options are based heavily on APL. See https://github.com/simulationcraft/simc/wiki/ActionLists. message APLRotation { - bool enabled = 20 [deprecated = true]; // If false, use old rotation options. - enum Type { TypeUnknown = 0; TypeAuto = 1; @@ -35,9 +33,6 @@ message APLPrepullAction { APLAction action = 1; APLValue do_at_value = 4; // When to perform this prepull action. Should be a negative value. bool hide = 3; // Causes this item to be ignored. - - // TODO: Remove after 1 month (on or after 8/30/2023). - string do_at = 2 [deprecated = true]; } message APLListItem { @@ -123,7 +118,6 @@ message APLValue { APLValueSpellIsReady spell_is_ready = 20; APLValueSpellTimeToReady spell_time_to_ready = 21; APLValueSpellCastTime spell_cast_time = 35; - APLValueSpellChannelTime spell_channel_time = 36; APLValueSpellTravelTime spell_travel_time = 37; APLValueSpellCPM spell_cpm = 42; APLValueSpellIsChanneling spell_is_channeling = 56; @@ -379,9 +373,6 @@ message APLValueSpellTimeToReady { message APLValueSpellCastTime { ActionID spell_id = 1; } -message APLValueSpellChannelTime { - ActionID spell_id = 1; -} message APLValueChannelClipDelay { } message APLValueFrontOfTarget { diff --git a/proto/common.proto b/proto/common.proto index 3eec7be001..48361eba7a 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -706,10 +706,6 @@ message UnitReference { // Reference to the owner, only used iff this is a pet. UnitReference owner = 4; - - // Raid index of the player to target. A value of -1 indicates no target. - // TODO: Delete this after 2 months (on or after 9/19/2023) - int32 target_index = 1 [deprecated = true]; } // ID for actions that aren't spells or items. @@ -763,9 +759,6 @@ message Cooldowns { // % HP threshold, below which defensive cooldowns can be used. double hp_percent_for_defensives = 2; - - int32 desync_proc_trinket1_seconds = 3 [deprecated = true]; - int32 desync_proc_trinket2_seconds = 4 [deprecated = true]; } message HealingModel { diff --git a/proto/druid.proto b/proto/druid.proto index eb10202919..ac0f5ec4c7 100644 --- a/proto/druid.proto +++ b/proto/druid.proto @@ -78,64 +78,7 @@ enum DruidRune { message BalanceDruid { message Rotation { - enum Type { - Unknown = 0; - Default = 1; - Manual = 2; - } - Type type = 1; - - enum MfUsage { - NoMf = 0; - BeforeLunar = 1; - MaximizeMf = 2; - MultidotMf = 3; - } - MfUsage mf_usage = 2; - - enum IsUsage { - NoIs = 0; - BeforeSolar = 1; - OptimizeIs = 2; - MultidotIs = 3; - } - IsUsage is_usage = 3; - - bool use_battle_res = 4; - enum WrathUsage { - NoWrath = 0; - FishingForLunar= 1; - RegularWrath = 2; - } - WrathUsage wrath_usage = 5; - bool use_starfire = 6; - bool use_typhoon = 7; - bool use_hurricane = 8; - bool use_smart_cooldowns = 9; - bool maintain_faerie_fire = 10; - int32 player_latency = 11; - float okf_ppm = 14 [deprecated = true]; - - enum EclipsePrio { - Lunar = 0; - Solar = 1; - } - EclipsePrio eclipse_prio = 12; - bool eclipse_shuffling = 13; - - enum MfExtension { - ExtendFishingForLunar = 0; - ExtendFishingForSolar = 1; - ExtendDuringLunar = 2; - ExtendDuringSolar = 3; - ExtendAlways = 4; - ExtendOutsideSolar = 5; - DontExtend = 6; - } - MfExtension mf_extension = 15; - bool snapshot_mf = 16; } - Rotation rotation = 1; message Options { UnitReference innervate_target = 1; @@ -146,14 +89,7 @@ message BalanceDruid { message FeralDruid { message Rotation { - bool maintain_faerie_fire = 1; - int32 min_combos_for_rip = 2; - float max_wait_time = 3; - float preroar_duration = 4; - bool precast_tigers_fury = 5; - bool use_shred_trick = 6; } - Rotation rotation = 1; message Options { UnitReference innervate_target = 1; @@ -165,16 +101,11 @@ message FeralDruid { message FeralTankDruid { message Rotation { - // Minimum rage to queue HS or Cleave. - int32 maul_rage_threshold = 1; - bool maintain_demoralizing_roar = 2; - double lacerate_time = 3; } - Rotation rotation = 1; message Options { UnitReference innervate_target = 1; - double starting_rage = 2; + double starting_rage = 2; } Options options = 3; } @@ -182,7 +113,6 @@ message FeralTankDruid { message RestorationDruid { message Rotation { } - Rotation rotation = 1; message Options { UnitReference innervate_target = 1; diff --git a/proto/hunter.proto b/proto/hunter.proto index 921db1173d..2b22bd2882 100644 --- a/proto/hunter.proto +++ b/proto/hunter.proto @@ -120,23 +120,7 @@ enum HunterRune { message Hunter { message Rotation { - enum RotationType { - UnknownType = 0; - SingleTarget = 1; - Aoe = 2; - Custom = 3; - } - RotationType type = 1; - - enum StingType { - NoSting = 0; - ScorpidSting = 1; - SerpentSting = 2; - } - StingType sting = 2; - bool multi_dot_serpent_sting = 3; } - Rotation rotation = 1; message Options { enum Ammo { diff --git a/proto/mage.proto b/proto/mage.proto index f6b3169c2f..6939a8eb30 100644 --- a/proto/mage.proto +++ b/proto/mage.proto @@ -78,13 +78,7 @@ enum MageRune { message Mage { message Rotation { - enum RotationType { - Unknown = 0; - Auto = 1; - } - RotationType rotation_type = 1; } - Rotation rotation = 1; message Options { enum ArmorType { diff --git a/proto/paladin.proto b/proto/paladin.proto index 213bcbc44f..0e685ad54e 100644 --- a/proto/paladin.proto +++ b/proto/paladin.proto @@ -3,8 +3,6 @@ package proto; option go_package = "./proto"; - import "common.proto"; - message PaladinTalents { // Holy int32 divine_strength = 1; @@ -87,40 +85,7 @@ enum PaladinJudgement { message RetributionPaladin { message Rotation { - int32 exo_slack = 1; - int32 cons_slack = 2; - double divine_plea_percentage = 3; - int32 holy_wrath_threshold = 4; - int32 sov_targets = 5; - - enum SpellOption { - NoSpell = 0; - JudgementOfWisdom = 1; - DivineStorm = 2; - HammerOfWrath = 3; - Consecration = 4; - HolyWrath = 5; - CrusaderStrike = 6; - Exorcism = 7; - DivinePlea = 8; - } - CustomRotation custom_rotation = 8; - CustomRotation custom_cast_sequence = 10; - - enum RotationType { - UnknownType = 0; - Standard = 1; - Custom = 2; - CastSequence = 3; - } - RotationType type = 9; - - bool use_divine_plea = 11; - bool avoid_clipping_consecration = 12; - bool hold_last_avenging_wrath_until_execution = 13; - bool cancel_chaos_bane = 14; } - Rotation rotation = 1; message Options { PaladinJudgement judgement = 1; @@ -133,26 +98,7 @@ message RetributionPaladin { message ProtectionPaladin { message Rotation { - bool hammer_first = 1; - bool use_custom_prio = 2; - bool squeeze_holy_wrath = 4; - double wait_slack = 5; - - enum SpellOption { - NoSpell = 0; - JudgementOfWisdom = 1; - HammerOfWrath = 2; - Consecration = 3; - HolyWrath = 4; - Exorcism = 5; - ShieldOfRighteousness = 6; - AvengersShield = 7; - HammerOfTheRighteous = 8; - HolyShield = 9; - } - CustomRotation custom_rotation = 3; } - Rotation rotation = 1; message Options { PaladinJudgement judgement = 1; @@ -166,7 +112,6 @@ message ProtectionPaladin { message HolyPaladin { message Rotation { } - Rotation rotation = 1; message Options { PaladinJudgement judgement = 1; diff --git a/proto/priest.proto b/proto/priest.proto index 4eec735755..6f11735608 100644 --- a/proto/priest.proto +++ b/proto/priest.proto @@ -72,23 +72,7 @@ enum PriestRune { message ShadowPriest { message Rotation { - enum RotationType { - Unknown = 0; - Basic = 1; - Clipping = 2; - Ideal = 3; - AoE = 4; - } - enum PreCastOption { - Nothing = 0; - PrecastVt = 1; - PrecastMb = 2; - } - RotationType rotation_type = 1; - PreCastOption precast_type = 2; - double latency = 3 [deprecated = true]; // Latency between actions } - Rotation rotation = 1; message Options { enum Armor { @@ -108,28 +92,7 @@ message ShadowPriest { message HealingPriest { message Rotation { - enum RotationType { - UnknownType = 0; - Cycle = 1; - Custom = 2; - } - RotationType type = 1; - - enum SpellOption { - NoSpell = 0; - GreaterHeal = 1; - FlashHeal = 2; - Renew = 3; - PowerWordShield = 4; - CircleOfHealing = 5; - PrayerOfHealing = 6; - PrayerOfMending = 7; - Penance = 8; - BindingHeal = 9; - } - CustomRotation custom_rotation = 2; } - Rotation rotation = 1; message Options { bool use_inner_fire = 3; diff --git a/proto/rogue.proto b/proto/rogue.proto index df7c0227d4..8774926e54 100644 --- a/proto/rogue.proto +++ b/proto/rogue.proto @@ -66,72 +66,7 @@ message RogueTalents { message Rogue { message Rotation { - enum Frequency { - Never = 0; - Once = 1; - Maintain = 2; - FrequencyUnknown = 5; - } - Frequency expose_armor_frequency = 1; - - int32 minimum_combo_points_expose_armor = 2; - - Frequency tricks_of_the_trade_frequency = 3; - - enum CombatPriority { - RuptureEviscerate = 0; - EviscerateRupture = 1; - CombatPriorityUnknown = 2; - } - CombatPriority combat_finisher_priority = 4; - - enum CombatBuilder { - SinisterStrike = 0; - Backstab = 1; - HemorrhageCombat = 2; - } - CombatBuilder combat_builder = 25; - - enum AssassinationPriority { - EnvenomRupture = 0; - RuptureEnvenom = 1; - AssassinationPriorityUnknown = 2; - } - AssassinationPriority assassination_finisher_priority = 5; - - enum SubtletyBuilder { - Hemorrhage = 0; - BackstabSub = 1; - } - SubtletyBuilder subtlety_builder = 26; - - enum SubtletyPriority { - SubtletyEviscerate = 0; - SubtletyEnvenom = 1; - SubtletyPriorityUnknown = 2; - } - SubtletyPriority subtlety_finisher_priority = 6; - - int32 minimum_combo_points_primary_finisher = 7; - int32 minimum_combo_points_secondary_finisher = 8; - - Frequency MultiTargetSliceFrequency = 9; - int32 minimum_combo_points_multi_target_slice = 10; - - reserved 11, 13, 14, 19, 21, 22, 23; // the various envenom pooling parameters - - bool use_feint = 12; - - bool open_with_garrote = 15; - bool open_with_premeditation = 16; - bool open_with_shadowstep = 17; - - bool rupture_for_bleed = 18; - - bool hemo_with_dagger = 20; - bool use_ghostly_strike = 24; } - Rotation rotation = 1; message Options { UnitReference tricks_of_the_trade_target = 1; diff --git a/proto/shaman.proto b/proto/shaman.proto index ba7284ec7c..4958a25644 100644 --- a/proto/shaman.proto +++ b/proto/shaman.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package proto; option go_package = "./proto"; -import "common.proto"; message ShamanTalents { // Elemental @@ -148,39 +147,10 @@ enum ShamanSyncType { message ElementalShaman { message Rotation { - // Deprecated as of 2023-08-29 - ShamanTotems totems = 3 [deprecated = true]; - - enum RotationType { - Unknown = 0; - Adaptive = 1; - Manual = 2; - } - RotationType type = 1; - bool in_thunderstorm_range = 2 [deprecated = true]; - - // These options are used for the manual rotation. - bool use_fire_nova = 4; - double fn_min_mana_per = 9; - bool use_chain_lightning = 5; - bool use_cl_only_gap = 11; - double cl_min_mana_per = 10; - bool overwrite_flameshock = 6; - bool always_crit_lvb = 7; - bool use_thunderstorm = 8; - double lvb_fs_wait_ms = 12; - - enum BloodlustUse { - UnsetBloodlust = 0; - UseBloodlust = 1; - NoBloodlust = 2; - } - BloodlustUse bloodlust = 13; } message Options { ShamanShield shield = 1; - bool bloodlust = 2 [deprecated = true]; ShamanTotems totems = 3; enum ThunderstormRange { @@ -190,72 +160,11 @@ message ElementalShaman { } ThunderstormRange thunderstormRange = 4; } - - Rotation rotation = 1; Options options = 3; } message EnhancementShaman { message Rotation { - // Deprecated as of 2023-08-29 - ShamanTotems totems = 1 [deprecated = true]; - - enum RotationType { - Unknown = 0; - Priority = 1; - Custom = 2; - } - RotationType rotation_type = 2; - - //TODO: add spells here for custom rotation (if necessary?) - enum CustomRotationSpell { - NoSpell = 0; - Stormstrike = 1; - StormstrikeDebuffMissing = 2; //find a way to differentiate tooltips for this, lightning bolt weaves, and downranked flametongue - LightningBolt = 3; - LightningBoltWeave = 4; - FlameShock = 5; - EarthShock = 6; - LavaLash = 7; - LavaBurst = 8; - LightningShield = 9; - FireNova = 10; - ChainLightning = 11; - FrostShock = 12; - MagmaTotem = 13; - LightningBoltDelayedWeave = 14; - } - CustomRotation custom_rotation = 3; - - //weaving options - bool lavaburst_weave = 4; - bool lightningbolt_weave = 5; - int32 maelstromweapon_min_stack = 6; - double auto_weave_delay = 7; - - //other general rotation config options - double firenova_mana_threshold = 8; - double shamanistic_rage_mana_threshold = 9; - - enum PrimaryShock { - None = 0; - Earth = 1; - Frost = 2; - } - - bool weave_flame_shock = 10; - PrimaryShock primary_shock = 11; - int32 flame_shock_clip_ticks = 12; - double delay_gcd_weave = 13; - ItemSwap item_swap = 14; - bool enable_item_swap = 15; - - enum BloodlustUse { - UnsetBloodlust = 0; - UseBloodlust = 1; - NoBloodlust = 2; - } - BloodlustUse bloodlust = 16; } message Options { @@ -266,8 +175,6 @@ message EnhancementShaman { ShamanImbue imbue_oh = 5; ShamanTotems totems = 6; } - - Rotation rotation = 1; Options options = 3; } @@ -280,28 +187,13 @@ enum ShamanHealSpell { message RestorationShaman { message Rotation { - // Deprecated as of 2023-08-29 - ShamanTotems totems = 1 [deprecated = true]; - bool use_earth_shield = 2; - ShamanHealSpell primary_heal = 3; - bool use_riptide = 4; - - enum BloodlustUse { - UnsetBloodlust = 0; - UseBloodlust = 1; - NoBloodlust = 2; - } - BloodlustUse bloodlust = 5; } message Options { ShamanShield shield = 1; - bool bloodlust = 2 [deprecated = true]; ShamanImbue imbue_mh = 4; int32 earth_shield_p_p_m = 5; ShamanTotems totems = 6; } - - Rotation rotation = 1; Options options = 3; } diff --git a/proto/ui.proto b/proto/ui.proto index 9bcd5f6f56..4df0914f30 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -238,8 +238,6 @@ message IndividualSimSettings { Player player = 3; Encounter encounter = 4; int32 target_dummies = 9; - // Deprecate after 2 months (on 2023/02/13) - repeated double ep_weights = 6; UnitStats ep_weights_stats = 10; repeated double ep_ratios = 11; Stat dps_ref_stat = 12; @@ -250,8 +248,6 @@ message IndividualSimSettings { // Local storage data for gear settings. message SavedGearSet { EquipmentSpec gear = 1; - // Deprecate after 2 months (on 2023/02/13) - repeated double bonus_stats = 2; UnitStats bonus_stats_stats = 3; } @@ -264,8 +260,6 @@ message SavedSettings { Consumes consumes = 5; Race race = 6; int32 level = 7; - Cooldowns cooldowns = 8; - string rotation_json = 9; repeated Profession professions = 10; bool enable_item_swap = 18; ItemSwap item_swap = 17; @@ -283,8 +277,6 @@ message SavedTalents { message SavedRotation { APLRotation rotation = 1; - string spec_rotation_options_json = 2; - Cooldowns cooldowns = 3; } message BlessingsAssignment { diff --git a/proto/warlock.proto b/proto/warlock.proto index b3adfbe7c2..ffed05af37 100644 --- a/proto/warlock.proto +++ b/proto/warlock.proto @@ -106,11 +106,9 @@ message WarlockOptions { } message Warlock { - WarlockRotation rotation = 1; WarlockOptions options = 2; } message TankWarlock { - WarlockRotation rotation = 1; WarlockOptions options = 2; } \ No newline at end of file diff --git a/proto/warrior.proto b/proto/warrior.proto index 19b683582b..c331b0fa2b 100644 --- a/proto/warrior.proto +++ b/proto/warrior.proto @@ -3,8 +3,6 @@ package proto; option go_package = "./proto"; -import "common.proto"; - message WarriorTalents { // Arms int32 improved_heroic_strike = 1; @@ -89,78 +87,7 @@ enum WarriorShout { message Warrior { message Rotation { - bool use_cleave = 1; - bool use_rend = 2; - bool use_ms = 18; - bool use_slam = 3; - - enum MainGcd { - None = 0; - Slam = 1; - Bloodthirst = 2; - Whirlwind = 3; - } - MainGcd main_gcd = 4; - - enum SunderArmor { - SunderArmorNone = 0; - SunderArmorHelpStack = 1; - SunderArmorMaintain = 2; - } - SunderArmor sunderArmor = 5; - bool maintain_demo_shout = 6; - bool maintain_thunder_clap = 7; - - // Queue HS or Cleave when over this threshold. - double hs_rage_threshold = 8; - - // Use Mortal Strike when over this threshold. - double ms_rage_threshold = 9; - - // Use Rend when below this threshold. - double rend_rage_threshold_below = 10; - - // Use Slam when over this threshold. - double slam_rage_threshold = 11; - - // Refresh Rend when remaining duration is less than this threshold. - double rend_cd_threshold = 12; - - bool use_hs_during_execute = 13; - bool use_bt_during_execute = 14; - bool spam_execute = 15; - bool use_ww_during_execute = 16; - bool use_slam_over_execute = 17; - - enum StanceOption { - DefaultStance = 0; - BattleStance = 1; - BerserkerStance = 2; - } - StanceOption stance_option = 19; - double rend_health_threshold_above = 20; - - // Only used for Fury - bool use_overpower = 21; - bool execute_phase_overpower = 22; - double bloodsurge_duration_threshold = 25; - - enum SpellOption { - NoSpell = 0; - BloodthirstCustom = 1; - MortalStrike = 2; - WhirlwindCustom = 3; - SlamCustom = 4; - Rend = 5; - Overpower = 6; - Execute = 7; - ThunderClap = 8; - SlamExpiring = 9; - } - CustomRotation custom_rotation = 23; - bool custom_rotation_option = 24; } - Rotation rotation = 1; message Options { double starting_rage = 1; @@ -175,39 +102,7 @@ message Warrior { message ProtectionWarrior { message Rotation { - enum DemoShoutChoice { - DemoShoutChoiceNone = 0; - DemoShoutChoiceMaintain = 1; - DemoShoutChoiceFiller = 2; - } - DemoShoutChoice demo_shout_choice = 1; - - enum ThunderClapChoice { - ThunderClapChoiceNone = 0; - ThunderClapChoiceMaintain = 1; - ThunderClapChoiceOnCD = 2; - } - ThunderClapChoice thunder_clap_choice = 2; - - enum SpellOption { - NoSpell = 0; - Revenge = 1; - ShieldSlam = 2; - Devastate = 3; - SunderArmor = 4; - Shout = 5; - DemoralizingShout = 6; - ThunderClap = 7; - MortalStrike = 8; - ConcussionBlow = 9; - Shockwave = 10; - } - CustomRotation custom_rotation = 3; - - double hs_rage_threshold = 4; - bool prio_sslam_on_shield_block = 5; } - Rotation rotation = 1; message Options { double starting_rage = 1; From c35297305e6757cd5643270e8d1a1d9c320be53f Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:47:38 -0500 Subject: [PATCH 02/26] update most sim/core for APL only --- sim/core/agent.go | 9 ---- sim/core/apl.go | 2 +- sim/core/apl_value.go | 2 - sim/core/apl_values_spell.go | 24 ---------- sim/core/attack.go | 16 ++----- sim/core/cast.go | 16 ++----- sim/core/character.go | 16 ------- sim/core/debuffs.go | 21 ++------- sim/core/dot.go | 8 ++-- sim/core/energy.go | 39 ++++++---------- sim/core/major_cooldown.go | 74 ----------------------------- sim/core/mana.go | 52 +++------------------ sim/core/pet.go | 1 - sim/core/rage.go | 19 ++------ sim/core/spell.go | 8 +--- sim/core/spell_snapshot.go | 90 ------------------------------------ sim/core/target_ai.go | 28 +---------- sim/core/target_dummy.go | 4 -- sim/core/unit.go | 14 +----- 19 files changed, 44 insertions(+), 399 deletions(-) delete mode 100644 sim/core/spell_snapshot.go diff --git a/sim/core/agent.go b/sim/core/agent.go index c570e5a4e8..905232b992 100644 --- a/sim/core/agent.go +++ b/sim/core/agent.go @@ -33,15 +33,6 @@ type Agent interface { // and once after the final iteration. Reset(sim *Simulation) - // Called whenever the GCD becomes ready for this Agent. - OnGCDReady(sim *Simulation) - - // Called after each auto attack performed by this Agent. - // This is different from Aura.OnSpellHit in that it is invoked fully after - // everything related to the attack is complete, and it is only invoked for - // auto attacks (white hits or white-hit-replacers). - OnAutoAttack(sim *Simulation, spell *Spell) - // Custom factories for APL values and actions, for cases where the value/action // involves class or spec-specific behavior. // diff --git a/sim/core/apl.go b/sim/core/apl.go index e4d3ff08cc..5d63a74b9b 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -54,7 +54,7 @@ func (rot *APLRotation) doAndRecordWarnings(warningsList *[]string, isPrepull bo } func (unit *Unit) newAPLRotation(config *proto.APLRotation) *APLRotation { - if config == nil || !unit.IsUsingAPL { + if config == nil { return nil } diff --git a/sim/core/apl_value.go b/sim/core/apl_value.go index a03572c390..bb10ce5cab 100644 --- a/sim/core/apl_value.go +++ b/sim/core/apl_value.go @@ -129,8 +129,6 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue { return rot.newValueSpellTimeToReady(config.GetSpellTimeToReady()) case *proto.APLValue_SpellCastTime: return rot.newValueSpellCastTime(config.GetSpellCastTime()) - case *proto.APLValue_SpellChannelTime: - return rot.newValueSpellChannelTime(config.GetSpellChannelTime()) case *proto.APLValue_SpellTravelTime: return rot.newValueSpellTravelTime(config.GetSpellTravelTime()) case *proto.APLValue_SpellCpm: diff --git a/sim/core/apl_values_spell.go b/sim/core/apl_values_spell.go index 32293cce0b..5c92018123 100644 --- a/sim/core/apl_values_spell.go +++ b/sim/core/apl_values_spell.go @@ -103,30 +103,6 @@ func (value *APLValueSpellCastTime) String() string { return fmt.Sprintf("Cast Time(%s)", value.spell.ActionID) } -type APLValueSpellChannelTime struct { - DefaultAPLValueImpl - spell *Spell -} - -func (rot *APLRotation) newValueSpellChannelTime(config *proto.APLValueSpellChannelTime) APLValue { - spell := rot.GetAPLSpell(config.SpellId) - if spell == nil { - return nil - } - return &APLValueSpellChannelTime{ - spell: spell, - } -} -func (value *APLValueSpellChannelTime) Type() proto.APLValueType { - return proto.APLValueType_ValueTypeDuration -} -func (value *APLValueSpellChannelTime) GetDuration(_ *Simulation) time.Duration { - return value.spell.Unit.ApplyCastSpeedForSpell(value.spell.DefaultCast.ChannelTime, value.spell) -} -func (value *APLValueSpellChannelTime) String() string { - return fmt.Sprintf("Channel Time(%s)", value.spell.ActionID) -} - type APLValueSpellTravelTime struct { DefaultAPLValueImpl spell *Spell diff --git a/sim/core/attack.go b/sim/core/attack.go index 2df0b88fd8..7c8a7f87f7 100644 --- a/sim/core/attack.go +++ b/sim/core/attack.go @@ -267,10 +267,9 @@ func (wa *WeaponAttack) swing(sim *Simulation) time.Duration { attackSpell := wa.spell if wa.replaceSwing != nil { - if wa.unit.IsUsingAPL { - // Need to check APL here to allow last-moment HS queue casts. - wa.unit.Rotation.DoNextAction(sim) - } + // Need to check APL here to allow last-moment HS queue casts. + wa.unit.Rotation.DoNextAction(sim) + // Allow MH swing to be overridden for abilities like Heroic Strike. attackSpell = wa.replaceSwing(sim, attackSpell) } @@ -282,11 +281,7 @@ func (wa *WeaponAttack) swing(sim *Simulation) time.Duration { attackSpell.Cast(sim, wa.unit.CurrentTarget) if !sim.Options.Interactive { - if wa.unit.IsUsingAPL { - wa.unit.Rotation.DoNextAction(sim) - } else { - wa.agent.OnAutoAttack(sim, attackSpell) - } + wa.unit.Rotation.DoNextAction(sim) } } else { // Delay till cast finishes if casting or 500 ms if not @@ -437,9 +432,6 @@ func (unit *Unit) EnableAutoAttacks(agent Agent, options AutoAttackOptions) { } } -// Empty handler so Agents don't have to provide one if they have no logic to add. -func (unit *Unit) OnAutoAttack(_ *Simulation, _ *Spell) {} - func (aa *AutoAttacks) finalize() { if aa.AutoSwingMelee { aa.mh.spell = aa.mh.unit.GetOrRegisterSpell(aa.mh.config) diff --git a/sim/core/cast.go b/sim/core/cast.go index 7dcc68a5a4..19c94525b4 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -48,9 +48,6 @@ type Cast struct { // The amount of time between the call to spell.Cast() and when the spell // effects are invoked. CastTime time.Duration - - // Additional GCD delay after the cast completes. - ChannelTime time.Duration } func (cast *Cast) EffectiveTime() time.Duration { @@ -59,15 +56,14 @@ func (cast *Cast) EffectiveTime() time.Duration { // TODO: isn't this wrong for spells like shadowfury, that have a reduced GCD? gcd = max(GCDMin, gcd) } - fullCastTime := cast.CastTime + cast.ChannelTime - return max(gcd, fullCastTime) + return max(gcd, cast.CastTime) } type CastFunc func(*Simulation, *Unit) type CastSuccessFunc func(*Simulation, *Unit) bool func (spell *Spell) castFailureHelper(sim *Simulation, gracefulFailure bool, message string, vals ...any) bool { - if sim.CurrentTime < 0 && spell.Unit.IsUsingAPL { + if sim.CurrentTime < 0 && spell.Unit.Rotation != nil { spell.Unit.Rotation.ValidationWarning(fmt.Sprintf(spell.ActionID.String()+" failed to cast: "+message, vals...)) } else if gracefulFailure { if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) { @@ -168,7 +164,6 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { if !config.IgnoreHaste { spell.CurCast.GCD = spell.Unit.ApplyCastSpeed(spell.CurCast.GCD) spell.CurCast.CastTime = config.CastTime(spell) - spell.CurCast.ChannelTime = spell.Unit.ApplyCastSpeedForSpell(spell.CurCast.ChannelTime, spell) } if config.CD.Timer != nil { @@ -204,10 +199,12 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { spell.Unit.SetGCDTimer(sim, sim.CurrentTime+effectiveTime) } + // TODO: Fix with removal of ChannelTime? if (spell.CurCast.CastTime > 0 || spell.CurCast.ChannelTime > 0) && spell.Unit.Moving { return spell.castFailureHelper(sim, false, "casting/channeling while moving not allowed!") } + // TODO: Fix with removal of ChannelTime? // Non melee casts if spell.Flags.Matches(SpellFlagResetAttackSwing) && spell.Unit.AutoAttacks.enabled { restartMeleeAt := sim.CurrentTime + spell.CurCast.CastTime + spell.CurCast.ChannelTime @@ -256,11 +253,6 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { return true } - // Instants/Channels - if spell.CurCast.ChannelTime > 0 { - spell.Unit.Hardcast = Hardcast{Expires: sim.CurrentTime + spell.CurCast.ChannelTime, ActionID: spell.ActionID, Pushback: 1.0} - } - if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) { spell.Unit.Log(sim, "Casting %s (Cost = %0.03f, Cast Time = %s, Effective Time = %s)", spell.ActionID, max(0, spell.CurCast.Cost), spell.CurCast.CastTime, spell.CurCast.EffectiveTime()) diff --git a/sim/core/character.go b/sim/core/character.go index 3a60d44655..a0e8c78c9d 100644 --- a/sim/core/character.go +++ b/sim/core/character.go @@ -106,10 +106,6 @@ func NewCharacter(party *Party, partyIndex int, player *proto.Player) Character ChannelClipDelay: max(0, time.Duration(player.ChannelClipDelayMs)*time.Millisecond), DistanceFromTarget: player.DistanceFromTarget, StartDistanceFromTarget: player.DistanceFromTarget, - IsUsingAPL: player.Rotation != nil && - (player.Rotation.Type == proto.APLRotation_TypeAPL || - player.Rotation.Type == proto.APLRotation_TypeAuto || - player.Rotation.Type == proto.APLRotation_TypeSimple), }, Name: player.Name, @@ -414,7 +410,6 @@ func (character *Character) initialize(agent Agent) { if sim.Options.Interactive { if character.GCD.IsReady(sim) { sim.NeedsInput = true - character.doNothing = false } return } @@ -423,17 +418,6 @@ func (character *Character) initialize(agent Agent) { character.Rotation.DoNextAction(sim) return } - - character.TryUseCooldowns(sim) - if character.GCD.IsReady(sim) { - agent.OnGCDReady(sim) - - if !character.doNothing && character.GCD.IsReady(sim) && (!character.IsWaiting() && !character.IsWaitingForMana()) { - msg := fmt.Sprintf("Character `%s` did not perform any actions. Either this is a bug or agent should use 'WaitUntil' or 'WaitForMana' to explicitly wait.\n\tIf character has no action to perform use 'DoNothing'.", character.Label) - panic(msg) - } - character.doNothing = false - } }, } } diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index f0e055fe9a..e02e579b8a 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -157,24 +157,9 @@ func ImprovedShadowBoltAura(unit *Unit, level int32) *Aura { } func ScheduledMajorArmorAura(aura *Aura, options PeriodicActionOptions, raid *proto.Raid) { - // Individual rogue sim rotation option messes with these debuff options, - // so it has to be handled separately. - allRogues := RaidPlayersWithClass(raid, proto.Class_ClassRogue) - singleExposeDelay := len(allRogues) == 1 && - allRogues[0].Spec.(*proto.Player_Rogue).Rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once - - if singleExposeDelay { - target := aura.Unit - exposeArmorAura := ExposeArmorAura(target, 2, raid.Parties[0].Players[0].Level) - exposeArmorAura.ApplyOnExpire(func(_ *Aura, sim *Simulation) { - aura.Duration = NeverExpires - StartPeriodicAction(sim, options) - }) - } else { - aura.OnReset = func(aura *Aura, sim *Simulation) { - aura.Duration = NeverExpires - StartPeriodicAction(sim, options) - } + aura.OnReset = func(aura *Aura, sim *Simulation) { + aura.Duration = NeverExpires + StartPeriodicAction(sim, options) } } diff --git a/sim/core/dot.go b/sim/core/dot.go index f66c3c7897..4ad4bf6e4a 100644 --- a/sim/core/dot.go +++ b/sim/core/dot.go @@ -204,7 +204,7 @@ func (dot *Dot) TickOnce(sim *Simulation) { dot.lastTickTime = sim.CurrentTime dot.OnTick(sim, dot.Unit, dot) - if dot.isChanneled && dot.Spell.Unit.IsUsingAPL { + if dot.isChanneled { // Note: even if the clip delay is 0ms, need a WaitUntil so that APL is called after the channel aura fully fades. if dot.MaxTicksRemaining() == 0 { if dot.Spell.Unit.GCD.IsReady(sim) { @@ -278,10 +278,8 @@ func newDot(config Dot) *Dot { } if dot.isChanneled { dot.Spell.Unit.ChanneledDot = nil - if dot.Spell.Unit.IsUsingAPL { - dot.Spell.Unit.Rotation.interruptChannelIf = nil - dot.Spell.Unit.Rotation.allowChannelRecastOnInterrupt = false - } + dot.Spell.Unit.Rotation.interruptChannelIf = nil + dot.Spell.Unit.Rotation.allowChannelRecastOnInterrupt = false } }) diff --git a/sim/core/energy.go b/sim/core/energy.go index 9a6311a6a2..6b0ec5878a 100644 --- a/sim/core/energy.go +++ b/sim/core/energy.go @@ -13,9 +13,6 @@ import ( const EnergyTickDuration = time.Millisecond * 2020 const EnergyPerTick = 20.2 -// OnEnergyGain is called any time energy is increased. -type OnEnergyGain func(sim *Simulation) - type energyBar struct { unit *Unit @@ -33,8 +30,6 @@ type energyBar struct { // Increments by 1 at each value of energyDecisionThresholds. cumulativeEnergyDecisionThresholds []int - onEnergyGain func(*Simulation, bool) - nextEnergyTick time.Duration // Multiplies energy regen from ticks. @@ -44,27 +39,12 @@ type energyBar struct { EnergyRefundMetrics *ResourceMetrics } -func (unit *Unit) EnableEnergyBar(maxEnergy float64, onEnergyGain OnEnergyGain) { +func (unit *Unit) EnableEnergyBar(maxEnergy float64) { unit.SetCurrentPowerBar(EnergyBar) unit.energyBar = energyBar{ - unit: unit, - maxEnergy: max(100, maxEnergy), - onEnergyGain: func(sim *Simulation, crossedThreshold bool) { - if sim.CurrentTime < 0 { - return - } - - if !sim.Options.Interactive && (!unit.IsWaitingForEnergy() || unit.DoneWaitingForEnergy(sim)) { - if unit.IsUsingAPL { - if crossedThreshold { - unit.Rotation.DoNextAction(sim) - } - } else { - onEnergyGain(sim) - } - } - }, + unit: unit, + maxEnergy: max(100, maxEnergy), EnergyTickMultiplier: 1, regenMetrics: unit.NewEnergyMetrics(ActionID{OtherID: proto.OtherAction_OtherActionEnergyRegen}), EnergyRefundMetrics: unit.NewEnergyMetrics(ActionID{OtherID: proto.OtherAction_OtherActionRefund}), @@ -76,9 +56,6 @@ func (eb *energyBar) setupEnergyThresholds() { if eb.unit == nil { return } - if !eb.unit.IsUsingAPL { - return - } var energyThresholds []int // Energy thresholds from spell costs. @@ -153,6 +130,16 @@ func (eb *energyBar) NextEnergyTickAt() time.Duration { return eb.nextEnergyTick } +func (eb *energyBar) onEnergyGain(sim *Simulation, crossedThreshold bool) { + if sim.CurrentTime < 0 { + return + } + + if !sim.Options.Interactive && crossedThreshold && (!eb.unit.IsWaitingForEnergy() || eb.unit.DoneWaitingForEnergy(sim)) { + eb.unit.Rotation.DoNextAction(sim) + } +} + func (eb *energyBar) addEnergyInternal(sim *Simulation, amount float64, metrics *ResourceMetrics) bool { if amount < 0 { panic("Trying to add negative energy!") diff --git a/sim/core/major_cooldown.go b/sim/core/major_cooldown.go index a5f53e0c16..f1d84b07dc 100644 --- a/sim/core/major_cooldown.go +++ b/sim/core/major_cooldown.go @@ -227,50 +227,6 @@ func (mcdm *majorCooldownManager) finalize() { mcdm.majorCooldowns = make([]*MajorCooldown, len(mcdm.initialMajorCooldowns)) } -// Adds a delay to the first usage of all CDs so that debuffs have time -// to be applied. MCDs that have a user-specified timing are not delayed. -// -// This function should be called from Agent.Init(). -func (mcdm *majorCooldownManager) DelayDPSCooldownsForArmorDebuffs(delay time.Duration) { - if mcdm.character.IsUsingAPL { - return - } - mcdm.character.Env.RegisterPostFinalizeEffect(func() { - for i := range mcdm.initialMajorCooldowns { - mcd := &mcdm.initialMajorCooldowns[i] - if len(mcd.timings) == 0 && mcd.Type.Matches(CooldownTypeDPS) && !mcd.Type.Matches(CooldownTypeExplosive) { - oldShouldActivate := mcd.ShouldActivate - mcd.ShouldActivate = func(sim *Simulation, character *Character) bool { - if oldShouldActivate != nil && !oldShouldActivate(sim, character) { - return false - } - return sim.CurrentTime >= delay - } - } - } - }) -} - -// Adds a delay to the first usage of all CDs overriding shouldActivate for cooldownTypeDPS, -// MCDs that have a user-specified timing are not delayed. -// This function should be called from Agent.Init(). -func (mcdm *majorCooldownManager) DelayDPSCooldowns(delay time.Duration) { - mcdm.character.Env.RegisterPostFinalizeEffect(func() { - for i := range mcdm.initialMajorCooldowns { - mcd := &mcdm.initialMajorCooldowns[i] - if len(mcd.timings) == 0 && mcd.Type.Matches(CooldownTypeDPS) { - oldShouldActivate := mcd.ShouldActivate - mcd.ShouldActivate = func(sim *Simulation, character *Character) bool { - if oldShouldActivate != nil && !oldShouldActivate(sim, character) { - return false - } - return sim.CurrentTime >= delay - } - } - } - }) -} - func findTrinketAura(character *Character, trinketID int32) *Aura { for _, aura := range character.auras { if aura.ActionIDForProc.ItemID == trinketID { @@ -377,36 +333,6 @@ func (mcdm *majorCooldownManager) getFirstReadyMCD(sim *Simulation) *MajorCooldo return nil } -func (mcdm *majorCooldownManager) TryUseCooldowns(sim *Simulation) { - if sim.CurrentTime < mcdm.minReady { - return - } - -restart: - for _, mcd := range mcdm.majorCooldowns { - if !mcd.IsReady(sim) { - break - } - - if mcd.tryActivateInternal(sim, mcdm.character) { - if mcd.IsReady(sim) { - continue // activation failed - } - mcdm.sort() - - if mcd.Spell.DefaultCast.GCD > 0 { - // If the GCD was used, don't use any more MCDs until the next cycle so - // their durations aren't partially wasted. - break - } - - // many MCDs are off the GCD, so it makes sense to continue - goto restart - } - } - mcdm.minReady = mcdm.majorCooldowns[0].ReadyAt() -} - // This function should be called if the CD for a major cooldown changes outside // of the TryActivate() call. func (mcdm *majorCooldownManager) UpdateMajorCooldowns() { diff --git a/sim/core/mana.go b/sim/core/mana.go index 645352ad45..57481992f3 100644 --- a/sim/core/mana.go +++ b/sim/core/mana.go @@ -10,12 +10,10 @@ import ( const ThreatPerManaGained = 0.5 -type OnManaTick func(sim *Simulation) type SpiritManaRegenPerSecond func() float64 type manaBar struct { unit *Unit - OnManaTick OnManaTick SpiritManaRegenPerSecond SpiritManaRegenPerSecond BaseMana float64 @@ -61,20 +59,6 @@ func (character *Character) EnableManaBarWithModifier(modifier float64) { character.Unit.manaBar.unit = &character.Unit } -// EnableResumeAfterManaWait will setup the OnManaTick callback to resume the given callback -// -// once enough mana has been gained after calling unit.WaitForMana() -func (character *Character) EnableResumeAfterManaWait(callback func(sim *Simulation)) { - if callback == nil { - panic("attempted to setup a mana tick callback that was nil") - } - character.OnManaTick = func(sim *Simulation) { - if character.FinishedWaitingForManaAndGCDReady(sim) { - callback(sim) - } - } -} - func (unit *Unit) HasManaBar() bool { return unit.manaBar.unit != nil } @@ -246,25 +230,24 @@ func (unit *Unit) TimeUntilManaRegen(desiredMana float64) time.Duration { } func (sim *Simulation) initManaTickAction() { - var playersWithManaBars []Agent - var petsWithManaBars []PetAgent + var unitsWithManaBars []*Unit for _, party := range sim.Raid.Parties { for _, player := range party.Players { character := player.GetCharacter() if character.HasManaBar() { - playersWithManaBars = append(playersWithManaBars, player) + unitsWithManaBars = append(unitsWithManaBars, &player.GetCharacter().Unit) } for _, petAgent := range character.PetAgents { if petAgent.GetPet().HasManaBar() { - petsWithManaBars = append(petsWithManaBars, petAgent) + unitsWithManaBars = append(unitsWithManaBars, &petAgent.GetCharacter().Unit) } } } } - if len(playersWithManaBars) == 0 && len(petsWithManaBars) == 0 { + if len(unitsWithManaBars) == 0 { return } @@ -274,30 +257,9 @@ func (sim *Simulation) initManaTickAction() { Priority: ActionPriorityRegen, } pa.OnAction = func(sim *Simulation) { - for _, player := range playersWithManaBars { - char := player.GetCharacter() - char.ManaTick(sim) - - if char.OnManaTick != nil { - // Only execute APL actions after mana ticks once pre-pull has completed. - if char.IsUsingAPL && sim.CurrentTime > 0 { - if char.IsWaitingForMana() && !char.DoneWaitingForMana(sim) { - continue - } - - char.Rotation.DoNextAction(sim) - } else { - char.OnManaTick(sim) - } - } - } - for _, petAgent := range petsWithManaBars { - pet := petAgent.GetPet() - if pet.IsEnabled() { - pet.ManaTick(sim) - if pet.OnManaTick != nil { - pet.OnManaTick(sim) - } + for _, unit := range unitsWithManaBars { + if unit.IsEnabled() { + unit.ManaTick(sim) } } diff --git a/sim/core/pet.go b/sim/core/pet.go index 2f294da575..82c44348d8 100644 --- a/sim/core/pet.go +++ b/sim/core/pet.go @@ -239,7 +239,6 @@ func (pet *Pet) Disable(sim *Simulation) { pet.focusBar.disable(sim) pet.AutoAttacks.CancelAutoSwing(sim) pet.enabled = false - pet.DoNothing() // mark it is as doing nothing now. // If a pet is immediately re-summoned it might try to use GCD, so we need to clear it. pet.Hardcast = Hardcast{} diff --git a/sim/core/rage.go b/sim/core/rage.go index dc34e384b5..e1caebd5ba 100644 --- a/sim/core/rage.go +++ b/sim/core/rage.go @@ -9,9 +9,6 @@ import ( const MaxRage = 100.0 const ThreatPerRageGained = 5 -// OnRageGain is called any time rage is increased. -type OnRageGain func(sim *Simulation) - // OnRageChange is called any time rage is increased. type OnRageChange func(aura *Aura, sim *Simulation, metrics *ResourceMetrics) @@ -21,8 +18,6 @@ type rageBar struct { startingRage float64 currentRage float64 - onRageGain OnRageGain - RageRefundMetrics *ResourceMetrics } @@ -33,7 +28,7 @@ type RageBarOptions struct { OHSwingSpeed float64 } -func (unit *Unit) EnableRageBar(options RageBarOptions, onRageGain OnRageGain) { +func (unit *Unit) EnableRageBar(options RageBarOptions) { rageFromDamageTakenMetrics := unit.NewRageMetrics(ActionID{OtherID: proto.OtherAction_OtherActionDamageTaken}) // Rage conversion is adjusted according to target stats (https://web.archive.org/web/20201118213002/https://blue.mmo-champion.com/topic/18325-the-new-rage-formula-by-kalgan/)\ // So this is probably only the base value formula and will be slightly wrong for most target @@ -91,10 +86,8 @@ func (unit *Unit) EnableRageBar(options RageBarOptions, onRageGain OnRageGain) { }) unit.rageBar = rageBar{ - unit: unit, - startingRage: max(0, min(options.StartingRage, MaxRage)), - onRageGain: onRageGain, - + unit: unit, + startingRage: max(0, min(options.StartingRage, MaxRage)), RageRefundMetrics: unit.NewRageMetrics(ActionID{OtherID: proto.OtherAction_OtherActionRefund}), } } @@ -123,11 +116,7 @@ func (rb *rageBar) AddRage(sim *Simulation, amount float64, metrics *ResourceMet rb.currentRage = newRage if !sim.Options.Interactive { - if rb.unit.IsUsingAPL { - rb.unit.Rotation.DoNextAction(sim) - } else { - rb.onRageGain(sim) - } + rb.unit.Rotation.DoNextAction(sim) } } diff --git a/sim/core/spell.go b/sim/core/spell.go index 46689da231..6153bc6e10 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -170,10 +170,6 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell { config.DamageMultiplier = 1 } - if unit.IsUsingAPL { - config.Cast.DefaultCast.ChannelTime = 0 - } - if (config.DamageMultiplier != 0 || config.ThreatMultiplier != 0) && config.ProcMask == ProcMaskUnknown { panic("ProcMask for spell " + config.ActionID.String() + " not set") } @@ -275,7 +271,7 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell { panic("Empty DefaultCast with a cost for spell " + config.ActionID.String()) } - if spell.DefaultCast.GCD == 0 && spell.DefaultCast.CastTime == 0 && spell.DefaultCast.ChannelTime == 0 { + if spell.DefaultCast.GCD == 0 && spell.DefaultCast.CastTime == 0 { config.Cast.IgnoreHaste = true } @@ -483,7 +479,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While moving only instant casts are possible - if (spell.DefaultCast.CastTime > 0 || spell.DefaultCast.ChannelTime > 0) && spell.Unit.Moving { + if (spell.DefaultCast.CastTime > 0) && spell.Unit.Moving { //if sim.Log != nil { // sim.Log("Cant cast because moving") //} diff --git a/sim/core/spell_snapshot.go b/sim/core/spell_snapshot.go deleted file mode 100644 index c4f445203c..0000000000 --- a/sim/core/spell_snapshot.go +++ /dev/null @@ -1,90 +0,0 @@ -package core - -import ( - "time" -) - -type procTracker struct { - aura *Aura - didActivate bool - isActive bool - expiresAt time.Duration -} - -type SnapshotManager struct { - procTrackers []*procTracker - majorCooldowns []*MajorCooldown - character *Character -} - -func NewSnapshotManager(character *Character) *SnapshotManager { - return &SnapshotManager{ - procTrackers: make([]*procTracker, 0), - majorCooldowns: make([]*MajorCooldown, 0), - character: character, - } -} - -func (manager *SnapshotManager) AddProc(id int32, label string, isActive bool) bool { - character := manager.character - - if !character.HasAura(label) { - return false - } - - manager.procTrackers = append(manager.procTrackers, &procTracker{ - didActivate: false, - isActive: isActive, - expiresAt: -1, - aura: character.GetAura(label), - }) - return true -} - -func (manager *SnapshotManager) CanSnapShot(sim *Simulation, castTime time.Duration) bool { - success := true - - for _, procTracker := range manager.procTrackers { - if !procTracker.didActivate && procTracker.aura.IsActive() { - procTracker.didActivate = true - procTracker.expiresAt = procTracker.aura.ExpiresAt() - } - - // A proc is about to drop - if procTracker.didActivate && procTracker.expiresAt <= sim.CurrentTime+castTime { - if sim.Log != nil { - sim.Log("Proc dropping " + procTracker.aura.Label) - } - return true - } - - if !procTracker.didActivate && !procTracker.isActive { - success = false - } - } - - return success -} - -func (manager *SnapshotManager) ActivateMajorCooldowns(sim *Simulation) { - for _, majorCd := range manager.majorCooldowns { - if majorCd.IsReady(sim) { - majorCd.TryActivate(sim, manager.character) - } - } -} - -func (manager *SnapshotManager) ResetProcTrackers() { - for _, procTracker := range manager.procTrackers { - procTracker.didActivate = false - procTracker.expiresAt = -1 - } -} - -func (manager *SnapshotManager) ClearMajorCooldowns() { - manager.majorCooldowns = make([]*MajorCooldown, 0) -} - -func (manager *SnapshotManager) AddMajorCooldown(majorCd *MajorCooldown) { - manager.majorCooldowns = append(manager.majorCooldowns, majorCd) -} diff --git a/sim/core/target_ai.go b/sim/core/target_ai.go index 33a20d8ff1..8a081cd11b 100644 --- a/sim/core/target_ai.go +++ b/sim/core/target_ai.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "log" "github.com/wowsims/sod/sim/core/proto" @@ -45,15 +44,7 @@ func (target *Target) initialize(config *proto.Target) { target.gcdAction = &PendingAction{ Priority: ActionPriorityGCD, OnAction: func(sim *Simulation) { - if target.GCD.IsReady(sim) { - target.OnGCDReady(sim) - - if !target.doNothing && target.GCD.IsReady(sim) && (!target.IsWaiting() && !target.IsWaitingForMana()) { - msg := fmt.Sprintf("Target `%s` did not perform any actions. Either this is a bug or agent should use 'WaitUntil' or 'WaitForMana' to explicitly wait.\n\tIf character has no action to perform use 'DoNothing'.", target.Label) - panic(msg) - } - target.doNothing = false - } + target.Rotation.DoNextAction(sim) }, } } @@ -67,23 +58,6 @@ func (target *Target) ApplyRunes() {} func (target *Target) GetCharacter() *Character { return nil } func (target *Target) Initialize() {} -func (target *Target) DoNothing() { - target.doNothing = true -} - -func (target *Target) OnAutoAttack(sim *Simulation, _ *Spell) { - if target.GCD.IsReady(sim) { - if target.AI != nil { - target.AI.DoAction(sim) - } - } -} -func (target *Target) OnGCDReady(sim *Simulation) { - if target.AI != nil { - target.AI.DoAction(sim) - } -} - type AIFactory func() TargetAI type PresetTarget struct { diff --git a/sim/core/target_dummy.go b/sim/core/target_dummy.go index 9a71903156..ff6bc1e0c7 100644 --- a/sim/core/target_dummy.go +++ b/sim/core/target_dummy.go @@ -49,7 +49,3 @@ func (td *TargetDummy) ApplyTalents() {} func (td *TargetDummy) ApplyRunes() {} func (td *TargetDummy) Initialize() {} func (td *TargetDummy) Reset(sim *Simulation) {} -func (td *TargetDummy) OnGCDReady(sim *Simulation) { - td.DoNothing() -} -func (td *TargetDummy) OnAutoAttack(sim *Simulation, spell *Spell) {} diff --git a/sim/core/unit.go b/sim/core/unit.go index 2a2e282f82..a2e5679428 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -123,8 +123,7 @@ type Unit struct { // Must be enabled to use, with "EnableAutoAttacks()". AutoAttacks AutoAttacks - IsUsingAPL bool // Used for checks before the finalize() stage, when apl rotations are created. - Rotation *APLRotation + Rotation *APLRotation // Statistics describing the results of the sim. Metrics UnitMetrics @@ -134,8 +133,7 @@ type Unit struct { AttackTables []*AttackTable DynamicDamageTakenModifiers []DynamicDamageTakenModifier - GCD *Timer - doNothing bool // flags that this character chose to do nothing. + GCD *Timer // Used for applying the effect of a hardcast spell when casting finishes. // For channeled spells, only Expires is set. @@ -174,14 +172,6 @@ func (unit *Unit) IsEnabled() bool { return unit.enabled } -// DoNothing will explicitly declare that the character is intentionally doing nothing. -// -// If the GCD is not used during OnGCDReady and this flag is set, OnGCDReady will not be called again -// until it is used in some other way (like from an auto attack or resource regeneration). -func (unit *Unit) DoNothing() { - unit.doNothing = true -} - func (unit *Unit) IsActive() bool { return unit.IsEnabled() && unit.CurrentHealthPercent() > 0 } From 8f793db3164035d0dbc3f1cb32a8ad6f722a8fba Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:47:48 -0500 Subject: [PATCH 03/26] update most sim/druid for APL only --- sim/druid/_hurricane.go | 3 +- sim/druid/_maul.go | 16 +--- sim/druid/_restoration/restoration.go | 6 +- sim/druid/_restoration/restoration_test.go | 1 - sim/druid/_restoration/rotation.go | 15 ---- sim/druid/_tank/rotation.go | 88 ---------------------- sim/druid/_tank/tank.go | 21 ++---- sim/druid/_tank/tank_test.go | 1 - sim/druid/balance/balance.go | 9 +-- sim/druid/balance/balance_test.go | 1 - sim/druid/balance/rotation.go | 24 ------ sim/druid/druid.go | 7 +- sim/druid/feral/feral.go | 8 +- sim/druid/feral/rotation.go | 1 - 14 files changed, 16 insertions(+), 185 deletions(-) delete mode 100644 sim/druid/_restoration/rotation.go delete mode 100644 sim/druid/_tank/rotation.go delete mode 100644 sim/druid/balance/rotation.go diff --git a/sim/druid/_hurricane.go b/sim/druid/_hurricane.go index 98d3fd258f..b3f3ff1e90 100644 --- a/sim/druid/_hurricane.go +++ b/sim/druid/_hurricane.go @@ -35,8 +35,7 @@ func (druid *Druid) registerHurricaneSpell() { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: time.Second * 10, + GCD: core.GCDDefault, }, }, Dot: core.DotConfig{ diff --git a/sim/druid/_maul.go b/sim/druid/_maul.go index 526a6a483b..b050a2a941 100644 --- a/sim/druid/_maul.go +++ b/sim/druid/_maul.go @@ -4,7 +4,7 @@ import ( "github.com/wowsims/sod/sim/core" ) -func (druid *Druid) registerMaulSpell(rageThreshold float64) { +func (druid *Druid) registerMaulSpell() { flatBaseDamage := 578.0 if druid.Ranged().ID == 23198 { // Idol of Brutality flatBaseDamage += 50 @@ -86,11 +86,6 @@ func (druid *Druid) registerMaulSpell(rageThreshold float64) { druid.MaulQueueAura.Activate(sim) }, }) - - druid.MaulRageThreshold = max(druid.Maul.DefaultCast.Cost, rageThreshold) - if druid.IsUsingAPL { - druid.MaulRageThreshold = 0 - } } func (druid *Druid) QueueMaul(sim *core.Simulation) { @@ -105,11 +100,6 @@ func (druid *Druid) MaulReplaceMH(sim *core.Simulation, mhSwingSpell *core.Spell return mhSwingSpell } - if druid.CurrentRage() < druid.MaulRageThreshold { - druid.MaulQueueAura.Deactivate(sim) - return mhSwingSpell - } - if !druid.Maul.Spell.CanCast(sim, druid.CurrentTarget) { druid.MaulQueueAura.Deactivate(sim) return mhSwingSpell @@ -117,7 +107,3 @@ func (druid *Druid) MaulReplaceMH(sim *core.Simulation, mhSwingSpell *core.Spell return druid.Maul.Spell } - -func (druid *Druid) ShouldQueueMaul(_ *core.Simulation) bool { - return druid.CurrentRage() >= druid.MaulRageThreshold -} diff --git a/sim/druid/_restoration/restoration.go b/sim/druid/_restoration/restoration.go index b54fde7f7d..4e086edd7a 100644 --- a/sim/druid/_restoration/restoration.go +++ b/sim/druid/_restoration/restoration.go @@ -28,8 +28,7 @@ func NewRestorationDruid(character *core.Character, options *proto.Player) *Rest selfBuffs := druid.SelfBuffs{} resto := &RestorationDruid{ - Druid: druid.New(character, druid.Tree, selfBuffs, options.TalentsString), - Rotation: restoOptions.Rotation, + Druid: druid.New(character, druid.Tree, selfBuffs, options.TalentsString), } resto.SelfBuffs.InnervateTarget = &proto.UnitReference{} @@ -37,14 +36,11 @@ func NewRestorationDruid(character *core.Character, options *proto.Player) *Rest resto.SelfBuffs.InnervateTarget = restoOptions.Options.InnervateTarget } - resto.EnableResumeAfterManaWait(resto.tryUseGCD) return resto } type RestorationDruid struct { *druid.Druid - - Rotation *proto.RestorationDruid_Rotation } func (resto *RestorationDruid) GetDruid() *druid.Druid { diff --git a/sim/druid/_restoration/restoration_test.go b/sim/druid/_restoration/restoration_test.go index 8c0bfe31a0..4cbba1196a 100644 --- a/sim/druid/_restoration/restoration_test.go +++ b/sim/druid/_restoration/restoration_test.go @@ -54,7 +54,6 @@ var PlayerOptionsStandard = &proto.Player_RestorationDruid{ Options: &proto.RestorationDruid_Options{ InnervateTarget: &proto.UnitReference{Type: proto.UnitReference_Player, Index: 0}, // self innervate }, - Rotation: &proto.RestorationDruid_Rotation{}, }, } diff --git a/sim/druid/_restoration/rotation.go b/sim/druid/_restoration/rotation.go deleted file mode 100644 index 8c5ca2849a..0000000000 --- a/sim/druid/_restoration/rotation.go +++ /dev/null @@ -1,15 +0,0 @@ -package restoration - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (resto *RestorationDruid) OnGCDReady(sim *core.Simulation) { - resto.tryUseGCD(sim) -} - -func (resto *RestorationDruid) tryUseGCD(sim *core.Simulation) { - resto.WaitUntil(sim, sim.CurrentTime+time.Second*5) -} diff --git a/sim/druid/_tank/rotation.go b/sim/druid/_tank/rotation.go deleted file mode 100644 index f391401b3b..0000000000 --- a/sim/druid/_tank/rotation.go +++ /dev/null @@ -1,88 +0,0 @@ -package tank - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (bear *FeralTankDruid) OnGCDReady(sim *core.Simulation) { - bear.doRotation(sim) -} - -func (bear *FeralTankDruid) OnAutoAttack(sim *core.Simulation, _ *core.Spell) { - bear.tryQueueMaul(sim) -} - -func (bear *FeralTankDruid) doRotation(sim *core.Simulation) { - if bear.GCD.IsReady(sim) { - if bear.shouldSaveLacerateStacks(sim) && bear.Lacerate.CanCast(sim, bear.CurrentTarget) { - bear.Lacerate.Cast(sim, bear.CurrentTarget) - } else if bear.shouldDemoRoar(sim) { - bear.DemoralizingRoar.Cast(sim, bear.CurrentTarget) - } else if bear.Berserk.IsReady(sim) { - bear.Berserk.Cast(sim, nil) - } else if bear.MangleBear.CanCast(sim, bear.CurrentTarget) { - bear.MangleBear.Cast(sim, bear.CurrentTarget) - } else if bear.shouldFaerieFire(sim) { - bear.FaerieFire.Cast(sim, bear.CurrentTarget) - } else if bear.shouldLacerate(sim) { - bear.Lacerate.Cast(sim, bear.CurrentTarget) - } else if bear.shouldSwipe(sim) { - bear.SwipeBear.Cast(sim, bear.CurrentTarget) - } - } - - if bear.GCD.IsReady(sim) { - nextAction := bear.FaerieFire.ReadyAt() - - if bear.MangleBear == nil { - bear.WaitUntil(sim, nextAction) - } else if !bear.MangleBear.IsReady(sim) { - nextAction = max(nextAction, sim.CurrentTime) - nextMangle := bear.MangleBear.ReadyAt() - - if nextMangle < nextAction+time.Second { - nextAction = nextMangle - } - - if nextAction > sim.CurrentTime { - bear.WaitUntil(sim, nextAction) - } - } - } - - bear.tryQueueMaul(sim) - bear.DoNothing() // means we intionally have no other action if all else fails. -} - -func (bear *FeralTankDruid) shouldSaveLacerateStacks(sim *core.Simulation) bool { - lacerateDot := bear.Lacerate.CurDot() - return lacerateDot.GetStacks() == 5 && - lacerateDot.RemainingDuration(sim) <= time.Millisecond*1500 -} - -func (bear *FeralTankDruid) shouldSwipe(sim *core.Simulation) bool { - return bear.SwipeBear.CanCast(sim, bear.CurrentTarget) && - ((bear.MangleBear == nil) || (bear.MangleBear.ReadyAt() >= sim.CurrentTime+core.GCDDefault)) && - bear.CurrentRage()-bear.SwipeBear.DefaultCast.Cost >= bear.MaulRageThreshold -} - -func (bear *FeralTankDruid) tryQueueMaul(sim *core.Simulation) { - if bear.ShouldQueueMaul(sim) { - bear.QueueMaul(sim) - } -} - -func (bear *FeralTankDruid) shouldDemoRoar(sim *core.Simulation) bool { - return bear.ShouldDemoralizingRoar(sim, false, bear.Rotation.MaintainDemoralizingRoar) -} - -func (bear *FeralTankDruid) shouldFaerieFire(sim *core.Simulation) bool { - return bear.FaerieFire.IsReady(sim) && ((bear.MangleBear == nil) || (bear.MangleBear.ReadyAt() >= sim.CurrentTime+time.Second)) -} - -func (bear *FeralTankDruid) shouldLacerate(sim *core.Simulation) bool { - lacerateDot := bear.Lacerate.CurDot() - return bear.Lacerate.CanCast(sim, bear.CurrentTarget) && ((bear.MangleBear == nil) || (bear.MangleBear.ReadyAt() >= sim.CurrentTime+core.GCDDefault)) && ((lacerateDot.GetStacks() < 5) || (lacerateDot.RemainingDuration(sim) <= time.Duration(bear.Rotation.LacerateTime*float64(time.Second)))) -} diff --git a/sim/druid/_tank/tank.go b/sim/druid/_tank/tank.go index 9a97992407..98b6e66888 100644 --- a/sim/druid/_tank/tank.go +++ b/sim/druid/_tank/tank.go @@ -28,9 +28,8 @@ func NewFeralTankDruid(character *core.Character, options *proto.Player) *FeralT selfBuffs := druid.SelfBuffs{} bear := &FeralTankDruid{ - Druid: druid.New(character, druid.Bear, selfBuffs, options.TalentsString), - Rotation: tankOptions.Rotation, - Options: tankOptions.Options, + Druid: druid.New(character, druid.Bear, selfBuffs, options.TalentsString), + Options: tankOptions.Options, } bear.SelfBuffs.InnervateTarget = &proto.UnitReference{} @@ -38,19 +37,10 @@ func NewFeralTankDruid(character *core.Character, options *proto.Player) *FeralT bear.SelfBuffs.InnervateTarget = tankOptions.Options.InnervateTarget } - rbo := core.RageBarOptions{ + bear.EnableRageBar(core.RageBarOptions{ StartingRage: bear.Options.StartingRage, RageMultiplier: 1, MHSwingSpeed: 2.5, - } - - bear.EnableRageBar(rbo, func(sim *core.Simulation) { - if bear.GCD.IsReady(sim) { - bear.TryUseCooldowns(sim) - if bear.GCD.IsReady(sim) { - bear.doRotation(sim) - } - } }) bear.EnableAutoAttacks(bear, core.AutoAttackOptions{ @@ -74,8 +64,7 @@ func NewFeralTankDruid(character *core.Character, options *proto.Player) *FeralT type FeralTankDruid struct { *druid.Druid - Rotation *proto.FeralTankDruid_Rotation - Options *proto.FeralTankDruid_Options + Options *proto.FeralTankDruid_Options } func (bear *FeralTankDruid) GetDruid() *druid.Druid { @@ -84,7 +73,7 @@ func (bear *FeralTankDruid) GetDruid() *druid.Druid { func (bear *FeralTankDruid) Initialize() { bear.Druid.Initialize() - bear.RegisterFeralTankSpells(float64(bear.Rotation.MaulRageThreshold)) + bear.RegisterFeralTankSpells() } func (bear *FeralTankDruid) Reset(sim *core.Simulation) { diff --git a/sim/druid/_tank/tank_test.go b/sim/druid/_tank/tank_test.go index 0e73f8bfa1..b85bf079f0 100644 --- a/sim/druid/_tank/tank_test.go +++ b/sim/druid/_tank/tank_test.go @@ -77,7 +77,6 @@ var PlayerOptionsDefault = &proto.Player_FeralTankDruid{ InnervateTarget: &proto.UnitReference{}, // no Innervate StartingRage: 20, }, - Rotation: &proto.FeralTankDruid_Rotation{}, }, } diff --git a/sim/druid/balance/balance.go b/sim/druid/balance/balance.go index 27628b8310..adda16c094 100644 --- a/sim/druid/balance/balance.go +++ b/sim/druid/balance/balance.go @@ -29,9 +29,8 @@ func NewBalanceDruid(character *core.Character, options *proto.Player) *BalanceD selfBuffs := druid.SelfBuffs{} moonkin := &BalanceDruid{ - Druid: druid.New(character, druid.Moonkin, selfBuffs, options.TalentsString), - Options: balanceOptions.Options, - Rotation: balanceOptions.Rotation, + Druid: druid.New(character, druid.Moonkin, selfBuffs, options.TalentsString), + Options: balanceOptions.Options, } moonkin.SelfBuffs.InnervateTarget = &proto.UnitReference{} @@ -39,7 +38,6 @@ func NewBalanceDruid(character *core.Character, options *proto.Player) *BalanceD moonkin.SelfBuffs.InnervateTarget = balanceOptions.Options.InnervateTarget } - moonkin.EnableResumeAfterManaWait(moonkin.tryUseGCD) return moonkin } @@ -51,8 +49,7 @@ type BalanceOnUseTrinket struct { type BalanceDruid struct { *druid.Druid - Options *proto.BalanceDruid_Options - Rotation *proto.BalanceDruid_Rotation + Options *proto.BalanceDruid_Options } func (moonkin *BalanceDruid) GetDruid() *druid.Druid { diff --git a/sim/druid/balance/balance_test.go b/sim/druid/balance/balance_test.go index 20de129e6b..38044bb53f 100644 --- a/sim/druid/balance/balance_test.go +++ b/sim/druid/balance/balance_test.go @@ -40,7 +40,6 @@ var PlayerOptionsAdaptive = &proto.Player_BalanceDruid{ Options: &proto.BalanceDruid_Options{ OkfUptime: 0.2, }, - Rotation: &proto.BalanceDruid_Rotation{}, }, } diff --git a/sim/druid/balance/rotation.go b/sim/druid/balance/rotation.go deleted file mode 100644 index 91bd3a5aec..0000000000 --- a/sim/druid/balance/rotation.go +++ /dev/null @@ -1,24 +0,0 @@ -package balance - -import ( - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/druid" -) - -func (moonkin *BalanceDruid) OnGCDReady(sim *core.Simulation) { - moonkin.tryUseGCD(sim) -} - -func (moonkin *BalanceDruid) tryUseGCD(sim *core.Simulation) { - spell, target := moonkin.rotation(sim) - if success := spell.Cast(sim, target); !success { - moonkin.WaitForMana(sim, spell.CurCast.Cost) - } -} - -func (moonkin *BalanceDruid) rotation(sim *core.Simulation) (*druid.DruidSpell, *core.Unit) { - moonkin.CurrentTarget = sim.Environment.GetTargetUnit(0) - target := moonkin.CurrentTarget - - return moonkin.Wrath, target -} diff --git a/sim/druid/druid.go b/sim/druid/druid.go index 285b2a4c0b..69018e573f 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -21,7 +21,6 @@ type Druid struct { StartingForm DruidForm - MaulRageThreshold float64 RebirthTiming float64 BleedsActive int AssumeBleedActive bool @@ -193,7 +192,7 @@ func (druid *Druid) RegisterFeralCatSpells() { // druid.registerEnrageSpell() // druid.registerFerociousBiteSpell() // druid.registerMangleBearSpell() - // druid.registerMaulSpell(0) + // druid.registerMaulSpell() // druid.registerLacerateSpell() // druid.registerRakeSpell() druid.registerRipSpell() @@ -204,7 +203,7 @@ func (druid *Druid) RegisterFeralCatSpells() { } // TODO: Classic feral tank -func (druid *Druid) RegisterFeralTankSpells(maulRageThreshold float64) { +func (druid *Druid) RegisterFeralTankSpells() { // druid.registerBarkskinCD() // druid.registerBerserkCD() // druid.registerBearFormSpell() @@ -212,7 +211,7 @@ func (druid *Druid) RegisterFeralTankSpells(maulRageThreshold float64) { // druid.registerEnrageSpell() // druid.registerFrenziedRegenerationCD() // druid.registerMangleBearSpell() - // druid.registerMaulSpell(maulRageThreshold) + // druid.registerMaulSpell() // druid.registerLacerateSpell() // druid.registerRakeSpell() // druid.registerRipSpell() diff --git a/sim/druid/feral/feral.go b/sim/druid/feral/feral.go index 1d4efb330b..b4369e0b6c 100644 --- a/sim/druid/feral/feral.go +++ b/sim/druid/feral/feral.go @@ -43,9 +43,9 @@ func NewFeralDruid(character *core.Character, options *proto.Player) *FeralDruid cat.maxRipTicks = 6 //cat.setupRotation(feralOptions.Rotation) - cat.EnableEnergyBar(100.0, cat.OnGCDReady) + cat.EnableEnergyBar(100.0) - cat.EnableRageBar(core.RageBarOptions{RageMultiplier: 1, MHSwingSpeed: 2.5}, func(sim *core.Simulation) {}) + cat.EnableRageBar(core.RageBarOptions{RageMultiplier: 1, MHSwingSpeed: 2.5}) cat.EnableAutoAttacks(cat, core.AutoAttackOptions{ // Base paw weapon. @@ -91,10 +91,6 @@ func (cat *FeralDruid) MissChance() float64 { func (cat *FeralDruid) Initialize() { cat.Druid.Initialize() cat.RegisterFeralCatSpells() - - if cat.IsUsingAPL { - return - } } func (cat *FeralDruid) Reset(sim *core.Simulation) { diff --git a/sim/druid/feral/rotation.go b/sim/druid/feral/rotation.go index 8421acd031..85b97e76e1 100644 --- a/sim/druid/feral/rotation.go +++ b/sim/druid/feral/rotation.go @@ -43,7 +43,6 @@ func (cat *FeralDruid) OnGCDReady(sim *core.Simulation) { // Replace gcd event with our own if we casted a spell if !cat.GCD.IsReady(sim) { nextGcd := cat.NextGCDAt() - cat.DoNothing() cat.CancelGCDTimer(sim) cat.NextRotationAction(sim, nextGcd) From 22da489bf67f78b8bc13005c5d1a3159f5cf16c7 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:48:08 -0500 Subject: [PATCH 04/26] remove custom_rotation.go --- sim/common/custom_rotation.go | 144 ---------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 sim/common/custom_rotation.go diff --git a/sim/common/custom_rotation.go b/sim/common/custom_rotation.go deleted file mode 100644 index 36277b712a..0000000000 --- a/sim/common/custom_rotation.go +++ /dev/null @@ -1,144 +0,0 @@ -package common - -import ( - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type CustomRotationType byte - -const ( - Basic CustomRotationType = iota - CPM -) - -// Custom condition for an action. -type CustomCondition func(*core.Simulation) bool - -// Custom action based on a condition. Returns a bool and the CurCast cost. -type CustomAction func(*core.Simulation, *core.Unit) (bool, float64) - -type CustomSpell struct { - Spell *core.Spell // Might be nil if this is not a spell action. - Action CustomAction - Condition CustomCondition - DesiredCPM float64 - - casts int // Number of casts thus far in the current iteration. -} - -type CustomRotation struct { - rotationType CustomRotationType - character *core.Character - spells []CustomSpell -} - -func NewCustomRotation(crProto *proto.CustomRotation, character *core.Character, spellsMap map[int32]CustomSpell) *CustomRotation { - if crProto == nil || len(crProto.Spells) == 0 { - return nil - } - - cr := &CustomRotation{ - rotationType: Basic, - character: character, - } - - for _, customSpellProto := range crProto.Spells { - customSpell := spellsMap[customSpellProto.Spell] - customSpell.DesiredCPM = customSpellProto.CastsPerMinute - if customSpell.DesiredCPM > 0 { - cr.rotationType = CPM - } - if customSpell.Action == nil && customSpell.Spell != nil { - spell := customSpell.Spell - customSpell.Action = func(sim *core.Simulation, target *core.Unit) (bool, float64) { - success := spell.Cast(sim, target) - return success, spell.CurCast.Cost - } - } - if customSpell.Condition == nil { - spell := customSpell.Spell - customSpell.Condition = func(sim *core.Simulation) bool { - return spell.CanCast(sim, character.CurrentTarget) - } - } - if customSpell.Action != nil { - cr.spells = append(cr.spells, customSpell) - } - } - - if len(cr.spells) == 0 { - return nil - } else { - cr.character.RegisterResetEffect(func(sim *core.Simulation) { - cr.reset(sim) - }) - return cr - } -} - -func (cr *CustomRotation) reset(_ *core.Simulation) { - for i := range cr.spells { - cr.spells[i].casts = 0 - } -} - -func (cr *CustomRotation) ChooseSpell(sim *core.Simulation) *CustomSpell { - if cr.rotationType == Basic { - return cr.chooseSpellBasic(sim) - } else { - return cr.chooseSpellCPM(sim) - } -} - -func (cr *CustomRotation) chooseSpellBasic(sim *core.Simulation) *CustomSpell { - for _, customSpell := range cr.spells { - if customSpell.Condition(sim) { - return &customSpell - } - } - return nil -} - -func (cr *CustomRotation) chooseSpellCPM(sim *core.Simulation) *CustomSpell { - for i := range cr.spells { - customSpell := &cr.spells[i] - if customSpell.CPM(sim) <= customSpell.DesiredCPM && customSpell.Condition(sim) { - return customSpell - } - } - return nil -} - -func (cs *CustomSpell) CPM(sim *core.Simulation) float64 { - if sim.CurrentTime == 0 { - return 0 - } - return float64(cs.casts) / (float64(sim.CurrentTime) / float64(time.Minute)) -} - -func (cr *CustomRotation) Cast(sim *core.Simulation) bool { - if cr == nil { - panic("Custom Rotation is empty") - } - - spell := cr.ChooseSpell(sim) - - if spell == nil { - cr.character.WaitUntil(sim, sim.CurrentTime+time.Millisecond*100) - return false - } - - success, cost := spell.Action(sim, cr.character.CurrentTarget) - if success { - spell.casts++ - } else { - if cr.character.HasManaBar() { - cr.character.WaitForMana(sim, cost) - } - } - - return true -} From 204dfebc199fdad643fb2aee07212a55b4a63471 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:53:14 -0500 Subject: [PATCH 05/26] update hunter APL only --- README.md | 5 +- sim/_hunter/rotation.go | 385 ------------------------------------ sim/_hunter/volley.go | 3 +- sim/hunter/hunter.go | 15 +- sim/hunter/pet.go | 44 ++--- sim/hunter/pet_abilities.go | 3 +- 6 files changed, 17 insertions(+), 438 deletions(-) delete mode 100644 sim/_hunter/rotation.go diff --git a/README.md b/README.md index aaf6336e77..2a6d1609d0 100644 --- a/README.md +++ b/README.md @@ -149,10 +149,7 @@ This project uses [Google Protocol Buffers](https://developers.google.com/protoc For a new sim, make the following changes: - Add a new value to the `Spec` enum in proto/common.proto. __NOTE: The name you give to this enum value is not just a name, it is used in our templating system. This guide will refer to this name as `$SPEC` elsewhere.__ - - Add a 'proto/YOUR_CLASS.proto' file if it doesn't already exist and add data messages containing all the class/spec-specific information needed to run your sim. In general, there will be 3 pieces of information you need: - - Talents - - Rotation (the order in which your sim will use spells/abilities) - - Options (additional choices your sim needs to make) + - Add a 'proto/YOUR_CLASS.proto' file if it doesn't already exist and add data messages containing all the class/spec-specific information needed to run your sim. - Update the `PlayerOptions.spec` field in `proto/api.proto` to include your shiny new message as an option. That's it! Now when you run `make` there will be generated .go and .ts code in `sim/core/proto` and `ui/core/proto` respectively. If you aren't familiar with protos, take a quick look at them to see what's happening. diff --git a/sim/_hunter/rotation.go b/sim/_hunter/rotation.go deleted file mode 100644 index 3ccde10d10..0000000000 --- a/sim/_hunter/rotation.go +++ /dev/null @@ -1,385 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/sod/sim/common" - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (hunter *Hunter) OnAutoAttack(sim *core.Simulation, spell *core.Spell) { - hunter.mayMoveAt = sim.CurrentTime - if !hunter.IsUsingAPL { - hunter.TryUseCooldowns(sim) - } - if hunter.GCD.IsReady(sim) { - hunter.rotation(sim) - } -} - -func (hunter *Hunter) OnGCDReady(sim *core.Simulation) { - hunter.rotation(sim) -} - -func (hunter *Hunter) rotation(sim *core.Simulation) { - if hunter.IsUsingAPL { - return - } - hunter.trySwapAspect(sim) - - if hunter.SilencingShot.IsReady(sim) { - hunter.SilencingShot.Cast(sim, hunter.CurrentTarget) - } - - if hunter.Rotation.Type == proto.Hunter_Rotation_Custom { - hunter.CustomRotation.Cast(sim) - } else if hunter.Rotation.Type == proto.Hunter_Rotation_Aoe { - spell := hunter.aoeChooseSpell(sim) - - success := spell.Cast(sim, hunter.CurrentTarget) - if !success { - hunter.WaitForMana(sim, spell.CurCast.Cost) - } - } else { - spell, target := hunter.singleTargetChooseSpell(sim) - if spell == nil { - if hunter.GCD.IsReady(sim) { - hunter.WaitUntil(sim, sim.CurrentTime+100*time.Millisecond) - } - } else { - success := spell.Cast(sim, target) - if !success { - hunter.WaitForMana(sim, spell.CurCast.Cost) - } - } - } -} - -func (hunter *Hunter) aoeChooseSpell(sim *core.Simulation) *core.Spell { - if hunter.Rotation.TrapWeave && hunter.ExplosiveTrap.IsReady(sim) && !hunter.ExplosiveTrap.AOEDot().IsActive() { - return hunter.TrapWeaveSpell - } else { - return hunter.Volley - } -} - -func (hunter *Hunter) singleTargetChooseSpell(sim *core.Simulation) (*core.Spell, *core.Unit) { - for _, spell := range hunter.rotationPriority { - if spell == nil { - continue - } - - for i := int32(0); i < hunter.Env.GetNumTargets(); i++ { - if hunter.rotationConditions[spell].CanUse(sim, hunter.Env.GetTargetUnit(i)) { - return spell, hunter.Env.GetTargetUnit(i) - } - } - } - return nil, nil -} - -// Returns whether an aspect was swapped. -func (hunter *Hunter) trySwapAspect(sim *core.Simulation) bool { - currentMana := hunter.CurrentManaPercent() - if hunter.currentAspect == hunter.AspectOfTheViperAura && hunter.Rotation.ViperStartManaPercent < 1 { - if !hunter.permaHawk && - hunter.CurrentMana() > hunter.manaSpentPerSecondAtFirstAspectSwap*sim.GetRemainingDuration().Seconds() { - hunter.permaHawk = true - } - if hunter.permaHawk || currentMana > hunter.Rotation.ViperStopManaPercent { - hunter.AspectOfTheDragonhawk.Cast(sim, nil) - return true - } - } else if hunter.currentAspect != hunter.AspectOfTheViperAura && !hunter.permaHawk && currentMana < hunter.Rotation.ViperStartManaPercent { - if hunter.manaSpentPerSecondAtFirstAspectSwap == 0 { - hunter.manaSpentPerSecondAtFirstAspectSwap = (hunter.Metrics.ManaSpent - hunter.Metrics.ManaGained) / sim.CurrentTime.Seconds() - } - if !hunter.permaHawk && - hunter.CurrentMana() > hunter.manaSpentPerSecondAtFirstAspectSwap*sim.GetRemainingDuration().Seconds() { - hunter.permaHawk = true - } - hunter.AspectOfTheViper.Cast(sim, nil) - return true - } - return false -} - -func (hunter *Hunter) shouldCastSteadyShot(sim *core.Simulation) bool { - for _, spell := range hunter.rotationPriority { - if spell == nil { - continue - } - ttr := spell.TimeToReady(sim) - if ttr > 0 && ttr < time.Duration(hunter.Rotation.SteadyShotMaxDelay)*time.Millisecond { - return false - } - } - return true -} - -func (hunter *Hunter) makeCustomRotation() *common.CustomRotation { - return common.NewCustomRotation(hunter.Rotation.CustomRotation, hunter.GetCharacter(), map[int32]common.CustomSpell{ - int32(proto.Hunter_Rotation_ArcaneShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.ArcaneShot.CurCast.Cost - return hunter.ArcaneShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.ArcaneShot.IsReady(sim) && (hunter.ExplosiveShotR4 == nil || (!hunter.ExplosiveShotR4.CurDot().IsActive() && !hunter.ExplosiveShotR3.CurDot().IsActive())) - }, - }, - int32(proto.Hunter_Rotation_AimedShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.AimedShot.CurCast.Cost - return hunter.AimedShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.AimedShot.IsReady(sim) - }, - }, - int32(proto.Hunter_Rotation_BlackArrow): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.BlackArrow.CurCast.Cost - return hunter.BlackArrow.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.BlackArrow.IsReady(sim) - }, - }, - int32(proto.Hunter_Rotation_ChimeraShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.ChimeraShot.CurCast.Cost - return hunter.ChimeraShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.ChimeraShot.IsReady(sim) - }, - }, - int32(proto.Hunter_Rotation_ExplosiveShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.ExplosiveShotR4.CurCast.Cost - return hunter.ExplosiveShotR4.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.ExplosiveShotR4.IsReady(sim) && !hunter.ExplosiveShotR4.CurDot().IsActive() - }, - }, - int32(proto.Hunter_Rotation_ExplosiveShotDownrank): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.ExplosiveShotR3.CurCast.Cost - return hunter.ExplosiveShotR3.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.ExplosiveShotR3.IsReady(sim) && !hunter.ExplosiveShotR3.CurDot().IsActive() - }, - }, - int32(proto.Hunter_Rotation_ExplosiveTrap): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.TrapWeaveSpell.CurCast.Cost - return hunter.TrapWeaveSpell.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.ExplosiveTrap.IsReady(sim) && !hunter.ExplosiveTrap.AOEDot().IsActive() - }, - }, - int32(proto.Hunter_Rotation_KillShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.KillShot.CurCast.Cost - return hunter.KillShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return sim.IsExecutePhase20() && hunter.KillShot.IsReady(sim) - }, - }, - int32(proto.Hunter_Rotation_MultiShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.MultiShot.CurCast.Cost - return hunter.MultiShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.MultiShot.IsReady(sim) - }, - }, - int32(proto.Hunter_Rotation_ScorpidStingSpell): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.ScorpidSting.CurCast.Cost - return hunter.ScorpidSting.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.Rotation.Sting == proto.Hunter_Rotation_ScorpidSting && !hunter.ScorpidStingAuras.Get(hunter.CurrentTarget).IsActive() - }, - }, - int32(proto.Hunter_Rotation_SerpentStingSpell): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.SerpentSting.CurCast.Cost - for i := int32(0); i < hunter.Env.GetNumTargets(); i++ { - target := hunter.Env.GetTargetUnit(i) - if !hunter.SerpentSting.Dot(target).IsActive() { - return hunter.SerpentSting.Cast(sim, target), cost - } - } - panic("No valid serpent-sting target found") - }, - Condition: func(sim *core.Simulation) bool { - if hunter.Rotation.Sting != proto.Hunter_Rotation_SerpentSting { - return false - } - if hunter.Rotation.MultiDotSerpentSting { - for i := int32(0); i < hunter.Env.GetNumTargets(); i++ { - if !hunter.SerpentSting.Dot(hunter.Env.GetTargetUnit(i)).IsActive() { - return true - } - } - } - return !hunter.SerpentSting.CurDot().IsActive() - }, - }, - int32(proto.Hunter_Rotation_SteadyShot): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.SteadyShot.CurCast.Cost - return hunter.SteadyShot.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return hunter.shouldCastSteadyShot(sim) - }, - }, - int32(proto.Hunter_Rotation_Volley): { - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - cost := hunter.Volley.CurCast.Cost - return hunter.Volley.Cast(sim, target), cost - }, - Condition: func(sim *core.Simulation) bool { - return true - }, - }, - }) -} - -type RotationCondition struct { - CanUse func(sim *core.Simulation, target *core.Unit) bool -} - -func (hunter *Hunter) initRotation() { - hunter.rotationConditions = map[*core.Spell]RotationCondition{ - hunter.KillShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return sim.IsExecutePhase20() && hunter.KillShot.IsReady(sim) - }, - }, - hunter.ExplosiveShotR4: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - if target != hunter.CurrentTarget { - return false - } - return hunter.ExplosiveShotR4.IsReady(sim) && !hunter.ExplosiveShotR4.CurDot().IsActive() - }, - }, - hunter.ExplosiveShotR3: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - if target != hunter.CurrentTarget { - return false - } - return hunter.Rotation.AllowExplosiveShotDownrank && hunter.ExplosiveShotR3.IsReady(sim) && !hunter.ExplosiveShotR3.CurDot().IsActive() - }, - }, - hunter.ScorpidSting: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - if target != hunter.CurrentTarget { - return false - } - return hunter.Rotation.Sting == proto.Hunter_Rotation_ScorpidSting && !hunter.ScorpidStingAuras.Get(hunter.CurrentTarget).IsActive() - }, - }, - hunter.SerpentSting: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - if target != hunter.CurrentTarget && !hunter.Rotation.MultiDotSerpentSting { - return false - } - return hunter.Rotation.Sting == proto.Hunter_Rotation_SerpentSting && !hunter.SerpentSting.Dot(target).IsActive() - }, - }, - hunter.ChimeraShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.ChimeraShot.IsReady(sim) - }, - }, - hunter.BlackArrow: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return !hunter.Rotation.TrapWeave && hunter.BlackArrow.IsReady(sim) - }, - }, - hunter.TrapWeaveSpell: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.Rotation.TrapWeave && hunter.ExplosiveTrap.IsReady(sim) && !hunter.ExplosiveTrap.AOEDot().IsActive() - }, - }, - hunter.AimedShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.AimedShot.IsReady(sim) - }, - }, - hunter.MultiShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.MultiShot.IsReady(sim) - }, - }, - hunter.ArcaneShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.ArcaneShot.IsReady(sim) && (!hunter.ExplosiveShotR4.CurDot().IsActive() && !hunter.ExplosiveShotR3.CurDot().IsActive()) - }, - }, - hunter.SteadyShot: RotationCondition{ - func(sim *core.Simulation, target *core.Unit) bool { - return hunter.SteadyShot.IsReady(sim) && hunter.shouldCastSteadyShot(sim) - }, - }, - } - - if hunter.PrimaryTalentTree == 0 { - // BM - hunter.rotationPriority = []*core.Spell{ - hunter.KillShot, - hunter.TrapWeaveSpell, - hunter.SerpentSting, - hunter.ScorpidSting, - hunter.AimedShot, - hunter.MultiShot, - hunter.SteadyShot, - } - } else if hunter.PrimaryTalentTree == 1 { - // MM - hunter.rotationPriority = []*core.Spell{ - hunter.KillShot, - hunter.SerpentSting, - hunter.ScorpidSting, - hunter.TrapWeaveSpell, - hunter.ChimeraShot, - hunter.AimedShot, - hunter.MultiShot, - hunter.SteadyShot, - } - } else { - // SV - hunter.rotationPriority = []*core.Spell{ - hunter.KillShot, - hunter.ExplosiveShotR4, - hunter.ExplosiveShotR3, - hunter.TrapWeaveSpell, - hunter.SerpentSting, - hunter.ScorpidSting, - hunter.BlackArrow, - hunter.AimedShot, - hunter.MultiShot, - hunter.SteadyShot, - } - } - - if hunter.Env.GetNumTargets() > 1 { - for i, spell := range hunter.rotationPriority { - if spell == hunter.AimedShot { - hunter.rotationPriority[i] = nil - } - } - } -} diff --git a/sim/_hunter/volley.go b/sim/_hunter/volley.go index 497466e288..cf02b3c9e4 100644 --- a/sim/_hunter/volley.go +++ b/sim/_hunter/volley.go @@ -19,8 +19,7 @@ func (hunter *Hunter) registerVolleySpell() { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: time.Second * 6, + GCD: core.GCDDefault, }, }, diff --git a/sim/hunter/hunter.go b/sim/hunter/hunter.go index 7e5d24acd8..e7c3a7d000 100644 --- a/sim/hunter/hunter.go +++ b/sim/hunter/hunter.go @@ -30,9 +30,8 @@ func RegisterHunter() { type Hunter struct { core.Character - Talents *proto.HunterTalents - Options *proto.Hunter_Options - Rotation *proto.Hunter_Rotation + Talents *proto.HunterTalents + Options *proto.Hunter_Options pet *HunterPet @@ -42,8 +41,6 @@ type Hunter struct { highestSerpentStingRank int - currentAspect *core.Aura - curQueueAura *core.Aura curQueuedAutoSpell *core.Spell @@ -133,10 +130,6 @@ func (hunter *Hunter) Initialize() { hunter.registerKillCommand() //hunter.registerRapidFireCD() - - if !hunter.IsUsingAPL { - hunter.DelayDPSCooldownsForArmorDebuffs(time.Second * 10) - } } func (hunter *Hunter) Reset(sim *core.Simulation) { @@ -149,10 +142,6 @@ func NewHunter(character *core.Character, options *proto.Player) *Hunter { Character: *character, Talents: &proto.HunterTalents{}, Options: hunterOptions.Options, - Rotation: hunterOptions.Rotation, - } - if hunter.Rotation == nil { - hunter.Rotation = &proto.Hunter_Rotation{} } core.FillTalentsProto(hunter.Talents.ProtoReflect(), options.TalentsString, TalentTreeSizes) hunter.EnableManaBar() diff --git a/sim/hunter/pet.go b/sim/hunter/pet.go index 269c450a6e..ad03365b67 100644 --- a/sim/hunter/pet.go +++ b/sim/hunter/pet.go @@ -116,47 +116,27 @@ func (hp *HunterPet) OnGCDReady(sim *core.Simulation) { if hp.hasOwnerCooldown && hp.CurrentFocus() < 50 { // When a major ability (Furious Howl or Savage Rend) is ready, pool enough // energy to use on-demand. - hp.DoNothing() return } target := hp.CurrentTarget - if hp.config.RandomSelection { - if sim.RandomFloat("Hunter Pet Ability") < 0.5 { - if !hp.specialAbility.CanCast(sim, target) || !hp.specialAbility.Cast(sim, target) { - if !hp.focusDump.Cast(sim, target) { - hp.DoNothing() - } - } - } else { - if !hp.focusDump.Cast(sim, target) { - if !hp.specialAbility.CanCast(sim, target) || !hp.specialAbility.Cast(sim, target) { - hp.DoNothing() - } - } - } + if hp.focusDump == nil { + hp.specialAbility.Cast(sim, target) + return + } + if hp.specialAbility == nil { + hp.focusDump.Cast(sim, target) return } - if !hp.specialAbility.CanCast(sim, target) || hp.specialAbility.Cast(sim, target) { - // For abilities that don't use the GCD. - if hp.GCD.IsReady(sim) { - if hp.focusDump != nil { - if !hp.focusDump.Cast(sim, target) { - hp.DoNothing() - } - } else { - hp.DoNothing() - } - } - } else { - if hp.focusDump != nil { - if !hp.focusDump.Cast(sim, target) { - hp.DoNothing() - } + if hp.config.RandomSelection { + if sim.RandomFloat("Hunter Pet Ability") < 0.5 { + _ = hp.specialAbility.Cast(sim, target) || hp.focusDump.Cast(sim, target) } else { - hp.DoNothing() + _ = hp.focusDump.Cast(sim, target) || hp.specialAbility.Cast(sim, target) } + } else { + _ = hp.specialAbility.Cast(sim, target) || hp.focusDump.Cast(sim, target) } } diff --git a/sim/hunter/pet_abilities.go b/sim/hunter/pet_abilities.go index f6453bd90d..d44ccf8ca3 100644 --- a/sim/hunter/pet_abilities.go +++ b/sim/hunter/pet_abilities.go @@ -274,8 +274,7 @@ func (hp *HunterPet) newPin() *core.Spell { Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: PetGCD, - ChannelTime: time.Second * 4, + GCD: PetGCD, }, IgnoreHaste: true, CD: core.Cooldown{ From a23f36b6e0dfc53d63a295f4ea4f0a587320012c Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:53:45 -0500 Subject: [PATCH 06/26] doNothing --- sim/lib/library.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/sim/lib/library.go b/sim/lib/library.go index 754f1da758..ebbdcd2548 100644 --- a/sim/lib/library.go +++ b/sim/lib/library.go @@ -180,8 +180,6 @@ func trySpell(act int) bool { //export doNothing func doNothing() bool { - player := _active_sim.Raid.Parties[0].Players[0] - player.GetCharacter().DoNothing() return true } From 7c4c2ce869171c1c2b944dc6cc280a237014c1d3 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 14:57:34 -0500 Subject: [PATCH 07/26] update mage APL only --- sim/mage/_blizzard.go | 3 +-- sim/mage/arcane_missiles.go | 4 +--- sim/mage/mage.go | 5 ++--- sim/mage/mage_test.go | 3 --- sim/mage/rotations.go | 17 ----------------- 5 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 sim/mage/rotations.go diff --git a/sim/mage/_blizzard.go b/sim/mage/_blizzard.go index 1aab945a1b..ba5ba3c301 100644 --- a/sim/mage/_blizzard.go +++ b/sim/mage/_blizzard.go @@ -57,8 +57,7 @@ func (mage *Mage) registerBlizzardSpell() { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: time.Second * 8, + GCD: core.GCDDefault, }, }, Dot: core.DotConfig{ diff --git a/sim/mage/arcane_missiles.go b/sim/mage/arcane_missiles.go index 4db5865b47..fef27ad42c 100644 --- a/sim/mage/arcane_missiles.go +++ b/sim/mage/arcane_missiles.go @@ -49,7 +49,6 @@ func (mage *Mage) getArcaneMissilesSpellConfig(rank int) core.SpellConfig { level := [9]int{0, 8, 16, 24, 32, 40, 48, 56, 56}[rank] tickLength := time.Second - channelTime := tickLength * time.Duration(numTicks) tickSpell := mage.getArcaneMissilesTickSpell(rank, numTicks, baseDotDamage) return core.SpellConfig{ @@ -63,8 +62,7 @@ func (mage *Mage) getArcaneMissilesSpellConfig(rank int) core.SpellConfig { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: channelTime, + GCD: core.GCDDefault, }, }, diff --git a/sim/mage/mage.go b/sim/mage/mage.go index 920aeaa00b..73900db46e 100644 --- a/sim/mage/mage.go +++ b/sim/mage/mage.go @@ -33,9 +33,8 @@ func RegisterMage() { type Mage struct { core.Character - Talents *proto.MageTalents - Options *proto.Mage_Options - Rotation *proto.Mage_Rotation + Talents *proto.MageTalents + Options *proto.Mage_Options ArcaneBlast *core.Spell ArcaneExplosion *core.Spell diff --git a/sim/mage/mage_test.go b/sim/mage/mage_test.go index 485589cbcf..b8e4d5a64b 100644 --- a/sim/mage/mage_test.go +++ b/sim/mage/mage_test.go @@ -101,7 +101,6 @@ func init() { // Options: &proto.Mage_Options{ // Armor: proto.Mage_Options_MageArmor, // }, -// Rotation: &proto.Mage_Rotation{}, // }, // } @@ -110,7 +109,6 @@ func init() { // Options: &proto.Mage_Options{ // Armor: proto.Mage_Options_MageArmor, // }, -// Rotation: &proto.Mage_Rotation{}, // }, // } @@ -119,7 +117,6 @@ func init() { // Options: &proto.Mage_Options{ // Armor: proto.Mage_Options_MageArmor, // }, -// Rotation: &proto.Mage_Rotation{}, // }, // } diff --git a/sim/mage/rotations.go b/sim/mage/rotations.go deleted file mode 100644 index d61c965f8b..0000000000 --- a/sim/mage/rotations.go +++ /dev/null @@ -1,17 +0,0 @@ -package mage - -import ( - "github.com/wowsims/sod/sim/core" -) - -func (mage *Mage) OnGCDReady(sim *core.Simulation) { - mage.tryUseGCD(sim) -} - -func (mage *Mage) tryUseGCD(sim *core.Simulation) { - if mage.IsUsingAPL { - return - } - - mage.DoNothing() -} From 688faa1ce5b6f2491265556c42721ac065eb06f0 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 15:09:36 -0500 Subject: [PATCH 08/26] update paladin APL only --- sim/_paladin/avenging_wrath.go | 8 - sim/_paladin/holy/holy.go | 8 +- sim/_paladin/holy/holy_test.go | 3 +- sim/_paladin/holy/rotation.go | 11 - sim/_paladin/paladin.go | 4 - sim/_paladin/protection/protection.go | 44 +-- sim/_paladin/protection/protection_test.go | 7 +- sim/_paladin/protection/rotation.go | 205 ----------- sim/_paladin/retribution/retribution.go | 109 +----- sim/_paladin/retribution/retribution_test.go | 11 - sim/_paladin/retribution/rotation.go | 355 ------------------- 11 files changed, 12 insertions(+), 753 deletions(-) delete mode 100644 sim/_paladin/holy/rotation.go delete mode 100644 sim/_paladin/protection/rotation.go delete mode 100644 sim/_paladin/retribution/rotation.go diff --git a/sim/_paladin/avenging_wrath.go b/sim/_paladin/avenging_wrath.go index b8ab80873f..3b2fa7ead9 100644 --- a/sim/_paladin/avenging_wrath.go +++ b/sim/_paladin/avenging_wrath.go @@ -49,14 +49,6 @@ func (paladin *Paladin) RegisterAvengingWrathCD() { Type: core.CooldownTypeDPS, // modify this logic if it should ever not be spammed on CD / maybe should synced with other CDs ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - if paladin.HoldLastAvengingWrathUntilExecution && float64(sim.CurrentTime+paladin.AvengingWrath.CD.Duration) >= float64(sim.Duration) { - if float64(sim.CurrentTime+paladin.AvengingWrathAura.Duration) >= float64(sim.Duration) { - // If we're cutting it close on iteration end time, pop AW anyways to try and get full duration. - return true - } - return false - } - if paladin.CurrentSeal == paladin.SealOfVengeanceAura { if paladin.SovDotSpell.Dot(paladin.CurrentTarget).GetStacks() < 5 { return false diff --git a/sim/_paladin/holy/holy.go b/sim/_paladin/holy/holy.go index 39d3a57bc6..b9cc366f67 100644 --- a/sim/_paladin/holy/holy.go +++ b/sim/_paladin/holy/holy.go @@ -27,9 +27,8 @@ func NewHolyPaladin(character *core.Character, options *proto.Player) *HolyPalad holyOptions := options.GetHolyPaladin() holy := &HolyPaladin{ - Paladin: paladin.NewPaladin(character, options.TalentsString), - Rotation: holyOptions.Rotation, - Options: holyOptions.Options, + Paladin: paladin.NewPaladin(character, options.TalentsString), + Options: holyOptions.Options, } holy.PaladinAura = holyOptions.Options.Aura @@ -40,8 +39,7 @@ func NewHolyPaladin(character *core.Character, options *proto.Player) *HolyPalad type HolyPaladin struct { *paladin.Paladin - Rotation *proto.HolyPaladin_Rotation - Options *proto.HolyPaladin_Options + Options *proto.HolyPaladin_Options } func (holy *HolyPaladin) GetPaladin() *paladin.Paladin { diff --git a/sim/_paladin/holy/holy_test.go b/sim/_paladin/holy/holy_test.go index 04c88d7e05..770752cd6d 100644 --- a/sim/_paladin/holy/holy_test.go +++ b/sim/_paladin/holy/holy_test.go @@ -78,8 +78,7 @@ var defaultProtOptions = &proto.HolyPaladin_Options{ var BasicOptions = &proto.Player_HolyPaladin{ HolyPaladin: &proto.HolyPaladin{ - Options: defaultProtOptions, - Rotation: &proto.HolyPaladin_Rotation{}, + Options: defaultProtOptions, }, } diff --git a/sim/_paladin/holy/rotation.go b/sim/_paladin/holy/rotation.go deleted file mode 100644 index 085bdd49c0..0000000000 --- a/sim/_paladin/holy/rotation.go +++ /dev/null @@ -1,11 +0,0 @@ -package holy - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (holy *HolyPaladin) OnGCDReady(sim *core.Simulation) { - holy.WaitUntil(sim, sim.CurrentTime+time.Second*5) -} diff --git a/sim/_paladin/paladin.go b/sim/_paladin/paladin.go index a5a092cfd5..d3fe6c566d 100644 --- a/sim/_paladin/paladin.go +++ b/sim/_paladin/paladin.go @@ -70,10 +70,6 @@ type Paladin struct { DemonAndUndeadTargetCount int32 - AvoidClippingConsecration bool - HoldLastAvengingWrathUntilExecution bool - CancelChaosBane bool - mutualLockoutDPAW *core.Timer } diff --git a/sim/_paladin/protection/protection.go b/sim/_paladin/protection/protection.go index 3a92346551..5a7cf26c10 100644 --- a/sim/_paladin/protection/protection.go +++ b/sim/_paladin/protection/protection.go @@ -1,8 +1,6 @@ package protection import ( - "time" - "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/paladin" @@ -29,23 +27,11 @@ func NewProtectionPaladin(character *core.Character, options *proto.Player) *Pro protOptions := options.GetProtectionPaladin() prot := &ProtectionPaladin{ - Paladin: paladin.NewPaladin(character, options.TalentsString), - Rotation: protOptions.Rotation, - Options: protOptions.Options, - Seal: protOptions.Options.Seal, + Paladin: paladin.NewPaladin(character, options.TalentsString), + Options: protOptions.Options, + Seal: protOptions.Options.Seal, } - var rotationInput = protOptions.Rotation.CustomRotation - - if rotationInput != nil { - prot.RotationInput = make([]int32, len(rotationInput.Spells)) - for i, customSpellProto := range rotationInput.Spells { - prot.RotationInput[i] = customSpellProto.Spell - } - } - - prot.SelectedRotation = prot.customRotation - prot.PaladinAura = protOptions.Options.Aura prot.EnableAutoAttacks(prot, core.AutoAttackOptions{ @@ -66,15 +52,11 @@ func NewProtectionPaladin(character *core.Character, options *proto.Player) *Pro type ProtectionPaladin struct { *paladin.Paladin - Rotation *proto.ProtectionPaladin_Rotation - Options *proto.ProtectionPaladin_Options + Options *proto.ProtectionPaladin_Options Judgement proto.PaladinJudgement Seal proto.PaladinSeal - - SelectedRotation func(*core.Simulation) - RotationInput []int32 } func (prot *ProtectionPaladin) GetPaladin() *paladin.Paladin { @@ -88,29 +70,11 @@ func (prot *ProtectionPaladin) Initialize() { if prot.Options.UseAvengingWrath { prot.RegisterAvengingWrathCD() } - - if !prot.IsUsingAPL { - prot.RegisterPrepullAction(-3*time.Second, func(sim *core.Simulation) { - prot.HolyShield.Cast(sim, nil) - }) - } - - if !prot.IsUsingAPL { - prot.RegisterPrepullAction(-1500*time.Millisecond, func(sim *core.Simulation) { - prot.DivinePlea.Cast(sim, nil) - }) - } } func (prot *ProtectionPaladin) Reset(sim *core.Simulation) { prot.Paladin.Reset(sim) - sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { - if isExecute == 20 { - prot.OnGCDReady(sim) - } - }) - switch prot.Seal { case proto.PaladinSeal_Vengeance: prot.CurrentSeal = prot.SealOfVengeanceAura diff --git a/sim/_paladin/protection/protection_test.go b/sim/_paladin/protection/protection_test.go index 2375c8e430..ff125e091a 100644 --- a/sim/_paladin/protection/protection_test.go +++ b/sim/_paladin/protection/protection_test.go @@ -32,7 +32,6 @@ func TestProtection(t *testing.T) { Seal: proto.PaladinSeal_Command, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: defaultProtRotation, }, }, }, @@ -45,7 +44,6 @@ func TestProtection(t *testing.T) { Seal: proto.PaladinSeal_Righteousness, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: defaultProtRotation, }, }, }, @@ -98,8 +96,6 @@ func BenchmarkSimulate(b *testing.B) { var StandardTalents = "-05005135200132311333312321-511302012003" -var defaultProtRotation = &proto.ProtectionPaladin_Rotation{} - var defaultProtOptions = &proto.ProtectionPaladin_Options{ Judgement: proto.PaladinJudgement_JudgementOfWisdom, Seal: proto.PaladinSeal_Vengeance, @@ -108,8 +104,7 @@ var defaultProtOptions = &proto.ProtectionPaladin_Options{ var DefaultOptions = &proto.Player_ProtectionPaladin{ ProtectionPaladin: &proto.ProtectionPaladin{ - Options: defaultProtOptions, - Rotation: defaultProtRotation, + Options: defaultProtOptions, }, } diff --git a/sim/_paladin/protection/rotation.go b/sim/_paladin/protection/rotation.go deleted file mode 100644 index 8ad08b6149..0000000000 --- a/sim/_paladin/protection/rotation.go +++ /dev/null @@ -1,205 +0,0 @@ -package protection - -import ( - "math" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (prot *ProtectionPaladin) OnGCDReady(sim *core.Simulation) { - if prot.IsUsingAPL { - return - } - - prot.SelectedRotation(sim) - - if prot.GCD.IsReady(sim) { - prot.DoNothing() - } -} - -func (prot *ProtectionPaladin) customRotation(sim *core.Simulation) { - // Setup - target := prot.CurrentTarget - - isExecutePhase := sim.IsExecutePhase20() - - // Forced CD remaining on HotR/ShoR to cast the other. Can't be exactly 3sec or lusted consecration GCDs will desync us. - gapSlack := time.Millisecond * 4000 - - // Allowed time to wait for HotR/ShoR to come off cooldown so we can cast them on cooldown and maintain 969. - maxWait := time.Duration(prot.Rotation.WaitSlack) * time.Millisecond - - // Helper vars since we call these repeatedly in many cases - nextHammer := prot.HammerOfTheRighteous.TimeToReady(sim) - nextShield := prot.ShieldOfRighteousness.TimeToReady(sim) - - if prot.GCD.IsReady(sim) { - - if !prot.Rotation.UseCustomPrio { - - // Standard rotation. Enforce 6sec CDs to have 1 GCD between, filling with 9sec abilities. - // HammerFirst flag flips ShoR and HotR in the rotation prio order - if prot.Rotation.HammerFirst && prot.HammerOfTheRighteous.IsReady(sim) { - // Always cast HotR if ready - prot.HammerOfTheRighteous.Cast(sim, target) - } else if prot.Rotation.HammerFirst && - prot.ShieldOfRighteousness.IsReady(sim) && - (nextHammer < gapSlack) { - // Cast ShoR if ready but only if you've spent a global since HotR - prot.ShieldOfRighteousness.Cast(sim, target) - } else if !prot.Rotation.HammerFirst && prot.ShieldOfRighteousness.IsReady(sim) { - // Always cast ShoR if ready - prot.ShieldOfRighteousness.Cast(sim, target) - } else if !prot.Rotation.HammerFirst && - prot.HammerOfTheRighteous.IsReady(sim) && - (nextShield < gapSlack) { - // Cast HotR if ready but only if you've spent a global since ShoR - prot.HammerOfTheRighteous.Cast(sim, target) - - // Maximum WaitSlack checking here to see if we should delay casting anything else because it will clip our 6 - // This callback method is probably inefficient, TODO perf improvement - } else if (nextHammer < maxWait) && (nextShield < gapSlack-maxWait) { - if sim.Log != nil { - prot.Log(sim, "Waiting %d ms to cast HotR...", int32(nextHammer.Milliseconds())) - } - prot.waitUntilNextEvent(sim, prot.customRotation) - } else if (nextShield < maxWait) && (nextHammer < gapSlack-maxWait) { - if sim.Log != nil { - prot.Log(sim, "Waiting %d ms to cast ShoR...", int32(nextShield.Milliseconds())) - } - prot.waitUntilNextEvent(sim, prot.customRotation) - - } else if isExecutePhase && prot.HammerOfWrath.IsReady(sim) { - // TODO: Prio may depend on gear; - prot.HammerOfWrath.Cast(sim, target) - } else if prot.HolyShield.IsReady(sim) { - // Top priority 9 is Holy Shield - prot.HolyShield.Cast(sim, target) - } else if prot.Consecration.IsReady(sim) { - prot.Consecration.Cast(sim, target) - } else if prot.Rotation.SqueezeHolyWrath && prot.HolyWrath.IsReady(sim) && (prot.Consecration.TimeToReady(sim) > time.Millisecond*6850) && (target.MobType == proto.MobType_MobTypeDemon || target.MobType == proto.MobType_MobTypeUndead) { - // Squeeze HW in open partial global after Consecration during bloodlust against Undead/Demon - prot.HolyWrath.Cast(sim, target) - } else if prot.JudgementOfWisdom.IsReady(sim) { - prot.JudgementOfWisdom.Cast(sim, target) - } - // Do not ever cast Exorcism AS - // TODO: Possible to dynamically affect Judgement<>AS priority based on Libram bonus at SBV softcap? - } else { - - // Custom rotation - rotationLoop: - for _, spellNumber := range prot.RotationInput { - // In priority order, fire the first spell which is Ready - // Still enforce Hammer/Shield being separated by a GCD - switch spellNumber { - case int32(proto.ProtectionPaladin_Rotation_JudgementOfWisdom): - if prot.JudgementOfWisdom.IsReady(sim) { - prot.JudgementOfWisdom.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_HammerOfWrath): - if isExecutePhase && prot.HammerOfWrath.IsReady(sim) { - prot.HammerOfWrath.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_Consecration): - if prot.Consecration.IsReady(sim) { - prot.Consecration.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_HolyWrath): - if prot.HolyWrath.IsReady(sim) { - prot.HolyWrath.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_Exorcism): - if prot.Exorcism.IsReady(sim) { - prot.Exorcism.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_ShieldOfRighteousness): - if prot.ShieldOfRighteousness.IsReady(sim) && (nextHammer < gapSlack) { - prot.ShieldOfRighteousness.Cast(sim, target) - break rotationLoop - } else if (nextShield < maxWait) && (nextHammer < gapSlack-maxWait) { - if sim.Log != nil { - prot.Log(sim, "Waiting %d ms to cast ShoR...", int32(nextShield.Milliseconds())) - } - prot.waitUntilNextEvent(sim, prot.customRotation) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_AvengersShield): - if prot.AvengersShield.IsReady(sim) { - prot.AvengersShield.Cast(sim, target) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_HammerOfTheRighteous): - if prot.HammerOfTheRighteous.IsReady(sim) && (nextShield < gapSlack) { - prot.HammerOfTheRighteous.Cast(sim, target) - break rotationLoop - } else if nextHammer < maxWait && (nextShield < gapSlack-maxWait) { - if sim.Log != nil { - prot.Log(sim, "Waiting %d ms to cast HotR...", int32(nextHammer.Milliseconds())) - } - prot.waitUntilNextEvent(sim, prot.customRotation) - break rotationLoop - } - case int32(proto.ProtectionPaladin_Rotation_HolyShield): - if prot.HolyShield.IsReady(sim) { - prot.HolyShield.Cast(sim, target) - break rotationLoop - } - } - } - - } - - } - - prot.waitUntilNextEvent(sim, prot.customRotation) - -} - -// Helper function for finding the next event -func (prot *ProtectionPaladin) waitUntilNextEvent(sim *core.Simulation, rotationCallback func(*core.Simulation)) { - // Find the minimum possible next event that is greater than the current time - nextEventAt := time.Duration(math.MaxInt64) // any event will happen before forever. - - // All possible next events - events := []time.Duration{ - prot.AutoAttacks.NextAttackAt(), - prot.GCD.ReadyAt(), - prot.JudgementOfWisdom.ReadyAt(), - prot.HammerOfWrath.ReadyAt(), - prot.Consecration.ReadyAt(), - prot.HolyWrath.ReadyAt(), - prot.Exorcism.ReadyAt(), - prot.ShieldOfRighteousness.ReadyAt(), - prot.AvengersShield.ReadyAt(), - prot.HammerOfTheRighteous.ReadyAt(), - prot.HolyShield.ReadyAt(), - } - - for _, elem := range events { - if elem > sim.CurrentTime && elem < nextEventAt { - nextEventAt = elem - } - } - // If the next action is the GCD, just return - if nextEventAt == prot.GCD.ReadyAt() { - return - } - - // Otherwise add a pending action for the next time - pa := &core.PendingAction{ - Priority: core.ActionPriorityLow, - OnAction: rotationCallback, - NextActionAt: nextEventAt, - } - - sim.AddPendingAction(pa) -} diff --git a/sim/_paladin/retribution/retribution.go b/sim/_paladin/retribution/retribution.go index 0d8a42431e..2126646669 100644 --- a/sim/_paladin/retribution/retribution.go +++ b/sim/_paladin/retribution/retribution.go @@ -1,8 +1,6 @@ package retribution import ( - "time" - "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/paladin" @@ -31,77 +29,24 @@ func NewRetributionPaladin(character *core.Character, options *proto.Player) *Re pal := paladin.NewPaladin(character, options.TalentsString) ret := &RetributionPaladin{ - Paladin: pal, - Rotation: retOptions.Rotation, - Judgement: retOptions.Options.Judgement, - Seal: retOptions.Options.Seal, - UseDivinePlea: retOptions.Rotation.UseDivinePlea, - AvoidClippingConsecration: retOptions.Rotation.AvoidClippingConsecration, - HoldLastAvengingWrathUntilExecution: retOptions.Rotation.HoldLastAvengingWrathUntilExecution, - DivinePleaPercentage: retOptions.Rotation.DivinePleaPercentage, - CancelChaosBane: retOptions.Rotation.CancelChaosBane, - ExoSlack: retOptions.Rotation.ExoSlack, - ConsSlack: retOptions.Rotation.ConsSlack, - HolyWrathThreshold: retOptions.Rotation.HolyWrathThreshold, - MaxSoVTargets: retOptions.Rotation.SovTargets, - HasLightswornBattlegear2Pc: character.HasSetBonus(paladin.ItemSetLightswornBattlegear, 2), + Paladin: pal, + Seal: retOptions.Options.Seal, } - pal.AvoidClippingConsecration = retOptions.Rotation.AvoidClippingConsecration - pal.HoldLastAvengingWrathUntilExecution = retOptions.Rotation.HoldLastAvengingWrathUntilExecution - pal.CancelChaosBane = retOptions.Rotation.CancelChaosBane - ret.PaladinAura = retOptions.Options.Aura - ret.RotatioOption = retOptions.Rotation.CustomRotation - if retOptions.Rotation.Type == proto.RetributionPaladin_Rotation_Standard { - ret.SelectedRotation = ret.mainRotation - } else if retOptions.Rotation.Type == proto.RetributionPaladin_Rotation_Custom { - ret.SelectedRotation = ret.customRotation - } else if retOptions.Rotation.Type == proto.RetributionPaladin_Rotation_CastSequence { - ret.SelectedRotation = ret.castSequenceRotation - ret.CastSequenceIndex = 0 - ret.RotatioOption = retOptions.Rotation.CustomCastSequence - } else { - ret.SelectedRotation = ret.mainRotation - } - ret.EnableAutoAttacks(ret, core.AutoAttackOptions{ MainHand: ret.WeaponFromMainHand(0), // Set crit multiplier later when we have targets. AutoSwingMelee: true, }) - ret.EnableResumeAfterManaWait(ret.OnGCDReady) - return ret } type RetributionPaladin struct { *paladin.Paladin - Judgement proto.PaladinJudgement - Seal proto.PaladinSeal - UseDivinePlea bool - AvoidClippingConsecration bool - HoldLastAvengingWrathUntilExecution bool - CancelChaosBane bool - - DivinePleaPercentage float64 - ExoSlack int32 - ConsSlack int32 - HolyWrathThreshold int32 - MaxSoVTargets int32 - - HasLightswornBattlegear2Pc bool - - SelectedJudgement *core.Spell - - SelectedRotation func(*core.Simulation) - RotatioOption *proto.CustomRotation - RotationInput []*core.Spell - CastSequenceIndex int32 - - Rotation *proto.RetributionPaladin_Rotation + Seal proto.PaladinSeal } func (ret *RetributionPaladin) GetPaladin() *paladin.Paladin { @@ -111,54 +56,11 @@ func (ret *RetributionPaladin) GetPaladin() *paladin.Paladin { func (ret *RetributionPaladin) Initialize() { ret.Paladin.Initialize() ret.RegisterAvengingWrathCD() - - if ret.Seal != proto.PaladinSeal_Vengeance { - ret.DelayDPSCooldownsForArmorDebuffs(time.Second * 10) - } } func (ret *RetributionPaladin) Reset(sim *core.Simulation) { ret.Paladin.Reset(sim) - switch ret.Judgement { - case proto.PaladinJudgement_JudgementOfWisdom: - ret.SelectedJudgement = ret.JudgementOfWisdom - case proto.PaladinJudgement_JudgementOfLight: - ret.SelectedJudgement = ret.JudgementOfLight - } - - if ret.RotatioOption != nil { - ret.RotationInput = make([]*core.Spell, 0, len(ret.RotatioOption.Spells)) - for _, customSpellProto := range ret.RotatioOption.Spells { - switch customSpellProto.Spell { - case int32(proto.RetributionPaladin_Rotation_JudgementOfWisdom): - ret.RotationInput = append(ret.RotationInput, ret.SelectedJudgement) - case int32(proto.RetributionPaladin_Rotation_DivineStorm): - ret.RotationInput = append(ret.RotationInput, ret.DivineStorm) - case int32(proto.RetributionPaladin_Rotation_HammerOfWrath): - ret.RotationInput = append(ret.RotationInput, ret.HammerOfWrath) - case int32(proto.RetributionPaladin_Rotation_Consecration): - ret.RotationInput = append(ret.RotationInput, ret.Consecration) - case int32(proto.RetributionPaladin_Rotation_HolyWrath): - ret.RotationInput = append(ret.RotationInput, ret.HolyWrath) - case int32(proto.RetributionPaladin_Rotation_CrusaderStrike): - ret.RotationInput = append(ret.RotationInput, ret.CrusaderStrike) - case int32(proto.RetributionPaladin_Rotation_Exorcism): - ret.RotationInput = append(ret.RotationInput, ret.Exorcism) - case int32(proto.RetributionPaladin_Rotation_DivinePlea): - ret.RotationInput = append(ret.RotationInput, ret.DivinePlea) - } - } - } - - sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { - if isExecute == 20 { - ret.OnGCDReady(sim) - } - }) - - ret.CastSequenceIndex = 0 - switch ret.Seal { case proto.PaladinSeal_Vengeance: ret.CurrentSeal = ret.SealOfVengeanceAura @@ -170,9 +72,4 @@ func (ret *RetributionPaladin) Reset(sim *core.Simulation) { ret.CurrentSeal = ret.SealOfRighteousnessAura ret.SealOfRighteousnessAura.Activate(sim) } - - if !ret.IsUsingAPL { - ret.DivinePleaAura.Activate(sim) - ret.DivinePlea.CD.Use(sim) - } } diff --git a/sim/_paladin/retribution/retribution_test.go b/sim/_paladin/retribution/retribution_test.go index 9d97bedae8..d411fcced8 100644 --- a/sim/_paladin/retribution/retribution_test.go +++ b/sim/_paladin/retribution/retribution_test.go @@ -32,7 +32,6 @@ func TestRetribution(t *testing.T) { Seal: proto.PaladinSeal_Command, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: &proto.RetributionPaladin_Rotation{}, }, }, }, @@ -45,7 +44,6 @@ func TestRetribution(t *testing.T) { Seal: proto.PaladinSeal_Righteousness, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: &proto.RetributionPaladin_Rotation{}, }, }, }, @@ -58,14 +56,6 @@ func TestRetribution(t *testing.T) { Seal: proto.PaladinSeal_Vengeance, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: &proto.RetributionPaladin_Rotation{ - ConsSlack: 500, - ExoSlack: 500, - UseDivinePlea: true, - DivinePleaPercentage: 0.75, - HolyWrathThreshold: 4, - SovTargets: 2, - }, }, }, }, @@ -123,7 +113,6 @@ var DefaultOptions = &proto.Player_RetributionPaladin{ Seal: proto.PaladinSeal_Vengeance, Aura: proto.PaladinAura_RetributionAura, }, - Rotation: &proto.RetributionPaladin_Rotation{}, }, } diff --git a/sim/_paladin/retribution/rotation.go b/sim/_paladin/retribution/rotation.go deleted file mode 100644 index 4677e1b1ab..0000000000 --- a/sim/_paladin/retribution/rotation.go +++ /dev/null @@ -1,355 +0,0 @@ -package retribution - -import ( - "math" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (ret *RetributionPaladin) OnAutoAttack(sim *core.Simulation, _ *core.Spell) { - if ret.SealOfVengeanceAura.IsActive() && min(ret.MaxSoVTargets, ret.Env.GetNumTargets()) > 1 { - minVengeanceDotDuration := time.Second * 15 - var minVengeanceDotDurationTarget *core.Unit - minVengeanceDotStacks := int32(5) - var minVengeanceDotStacksTarget *core.Unit - for i := int32(0); i < min(ret.MaxSoVTargets, ret.Env.GetNumTargets()); i++ { - target := ret.Env.GetTargetUnit(i) - dot := ret.SovDotSpell.Dot(target) - remainingDuration := dot.RemainingDuration(sim) - stackCount := dot.GetStacks() - - if remainingDuration < minVengeanceDotDuration && remainingDuration > 0 { - minVengeanceDotDuration = remainingDuration - minVengeanceDotDurationTarget = target - } - - if stackCount < minVengeanceDotStacks { - minVengeanceDotStacks = stackCount - minVengeanceDotStacksTarget = target - } - } - - if minVengeanceDotDuration < core.DurationFromSeconds(ret.AutoAttacks.MH().SwingSpeed*2) { - ret.CurrentTarget = minVengeanceDotDurationTarget - } else if ret.SovDotSpell.Dot(ret.CurrentTarget).GetStacks() == 5 && minVengeanceDotStacks < 5 { - ret.CurrentTarget = minVengeanceDotStacksTarget - } else { - ret.CurrentTarget = ret.Env.Encounter.TargetUnits[0] - } - } -} - -func (ret *RetributionPaladin) OnGCDReady(sim *core.Simulation) { - if ret.IsUsingAPL { - return - } - - ret.SelectedRotation(sim) - if ret.GCD.IsReady(sim) { - ret.DoNothing() // this means we had nothing to do and we are ok - } -} - -func (ret *RetributionPaladin) customRotation(sim *core.Simulation) { - // Setup - target := ret.Env.Encounter.TargetUnits[0] - - nextSwingAt := ret.AutoAttacks.NextAttackAt() - isExecutePhase := sim.IsExecutePhase20() - - if ret.HandOfReckoning != nil && ret.HandOfReckoning.IsReady(sim) { - ret.HandOfReckoning.Cast(sim, ret.CurrentTarget) - } - - if ret.GCD.IsReady(sim) { - rotationLoop: - for _, spell := range ret.RotationInput { - if spell == ret.HammerOfWrath && !isExecutePhase { - continue - } - - if spell == ret.HammerOfWrath && isExecutePhase && ret.HoldLastAvengingWrathUntilExecution { - if ret.AvengingWrath.IsReady(sim) { - success := ret.AvengingWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.AvengingWrath.CurCast.Cost) - } - } - } - - if spell == ret.HolyWrath { - // Holy Wrath isn't worth casting if it will reduce usages of CS/DS - if ret.CrusaderStrike.ReadyAt()-sim.CurrentTime < 500*time.Millisecond { - continue - } - if ret.DivineStorm.ReadyAt()-sim.CurrentTime < 500*time.Millisecond { - continue - } - } - - if spell == ret.Consecration && !ret.checkConsecrationClipping(sim) { - // This is a skip, so we take the opposite of the clip check. - continue - } - - if spell == ret.Exorcism && !ret.ArtOfWarInstantCast.IsActive() { - continue - } - - if spell == ret.DivinePlea && ret.CurrentMana() > (ret.MaxMana()*ret.DivinePleaPercentage) { - continue - } - - if spell.IsReady(sim) { - success := spell.Cast(sim, target) - if !success { - ret.WaitForMana(sim, spell.CurCast.Cost) - } - break rotationLoop - } - } - } - - // All possible next events - events := []time.Duration{ - nextSwingAt, - ret.GCD.ReadyAt(), - ret.SelectedJudgement.CD.ReadyAt(), - ret.DivineStorm.CD.ReadyAt(), - ret.HammerOfWrath.CD.ReadyAt(), - ret.HolyWrath.CD.ReadyAt(), - ret.CrusaderStrike.CD.ReadyAt(), - ret.Consecration.CD.ReadyAt(), - ret.Exorcism.CD.ReadyAt(), - ret.DivinePlea.CD.ReadyAt(), - } - - if ret.HandOfReckoning != nil { - events = append(events, ret.HandOfReckoning.CD.ReadyAt()) - } - - CancelChaosBane(ret, sim) - ret.waitUntilNextEvent(sim, events, ret.customRotation) -} - -func (ret *RetributionPaladin) castSequenceRotation(sim *core.Simulation) { - if len(ret.RotationInput) == 0 { - return - } - - // Setup - target := ret.Env.Encounter.TargetUnits[0] - isExecutePhase := sim.IsExecutePhase20() - - nextReadyAt := sim.CurrentTime - - if hc := ret.Hardcast; ret.HandOfReckoning != nil && ret.HandOfReckoning.IsReady(sim) && !(hc.Expires > sim.CurrentTime) { - ret.HandOfReckoning.Cast(sim, ret.CurrentTarget) - } - - if ret.GCD.IsReady(sim) { - if ret.UseDivinePlea && ret.DivinePlea.IsReady(sim) && ret.CurrentMana() < (ret.MaxMana()*ret.DivinePleaPercentage) { - ret.DivinePlea.Cast(sim, nil) - } else { - currentSpell := ret.RotationInput[ret.CastSequenceIndex] - - if currentSpell == ret.HammerOfWrath && !isExecutePhase { - return - } - - if currentSpell.IsReady(sim) { - success := currentSpell.Cast(sim, target) - if success { - ret.CastSequenceIndex = (ret.CastSequenceIndex + 1) % int32(len(ret.RotationInput)) - } else { - ret.WaitForMana(sim, currentSpell.CurCast.Cost) - } - } else { - nextReadyAt = currentSpell.ReadyAt() - } - } - } - - events := []time.Duration{ - ret.GCD.ReadyAt(), - nextReadyAt, - } - - if ret.HandOfReckoning != nil { - events = append(events, ret.HandOfReckoning.CD.ReadyAt()) - } - - CancelChaosBane(ret, sim) - ret.waitUntilNextEvent(sim, events, ret.castSequenceRotation) -} - -func (ret *RetributionPaladin) mainRotation(sim *core.Simulation) { - - // Setup - target := ret.Env.Encounter.TargetUnits[0] - - nextSwingAt := ret.AutoAttacks.NextAttackAt() - isExecutePhase := sim.IsExecutePhase20() - - nextPrimaryAbility := min(ret.CrusaderStrike.CD.ReadyAt(), ret.DivineStorm.CD.ReadyAt(), ret.SelectedJudgement.CD.ReadyAt()) - nextPrimaryAbilityDelta := nextPrimaryAbility - sim.CurrentTime - - if ret.HandOfReckoning != nil && ret.HandOfReckoning.IsReady(sim) { - ret.HandOfReckoning.Cast(sim, ret.CurrentTarget) - } - - if ret.GCD.IsReady(sim) { - switch { - case isExecutePhase && ret.HammerOfWrath.IsReady(sim) && ret.HoldLastAvengingWrathUntilExecution: - if ret.AvengingWrath.IsReady(sim) { - success := ret.AvengingWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.AvengingWrath.CurCast.Cost) - } - } - - success := ret.HammerOfWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.HammerOfWrath.CurCast.Cost) - } - case ret.SelectedJudgement.IsReady(sim): - success := ret.SelectedJudgement.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.SelectedJudgement.CurCast.Cost) - } - case ret.HasLightswornBattlegear2Pc && ret.DivineStorm.IsReady(sim): - success := ret.DivineStorm.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.DivineStorm.CurCast.Cost) - } - case ret.Env.GetNumTargets() > 1 && ret.Consecration.IsReady(sim): - success := ret.Consecration.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.Consecration.CurCast.Cost) - } - case isExecutePhase && ret.HammerOfWrath.IsReady(sim): - success := ret.HammerOfWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.HammerOfWrath.CurCast.Cost) - } - case ret.DemonAndUndeadTargetCount >= ret.HolyWrathThreshold && ret.HolyWrath.IsReady(sim): - success := ret.HolyWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.HolyWrath.CurCast.Cost) - } - case ret.UseDivinePlea && ret.CurrentMana() < (ret.MaxMana()*ret.DivinePleaPercentage) && ret.DivinePlea.IsReady(sim): - ret.DivinePlea.Cast(sim, nil) - case ret.CrusaderStrike.IsReady(sim): - success := ret.CrusaderStrike.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.CrusaderStrike.CurCast.Cost) - } - case ret.DivineStorm.IsReady(sim): - success := ret.DivineStorm.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.DivineStorm.CurCast.Cost) - } - case (target.MobType == proto.MobType_MobTypeDemon || target.MobType == proto.MobType_MobTypeUndead) && - nextPrimaryAbilityDelta.Milliseconds() > int64(ret.ExoSlack) && ret.Exorcism.IsReady(sim) && ret.ArtOfWarInstantCast.IsActive(): - success := ret.Exorcism.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.Exorcism.CurCast.Cost) - } - case nextPrimaryAbilityDelta.Milliseconds() > int64(ret.ConsSlack) && ret.Consecration.IsReady(sim) && ret.checkConsecrationClipping(sim): - success := ret.Consecration.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.Consecration.CurCast.Cost) - } - case nextPrimaryAbilityDelta.Milliseconds() > int64(ret.ExoSlack) && ret.Exorcism.IsReady(sim) && ret.ArtOfWarInstantCast.IsActive(): - success := ret.Exorcism.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.Exorcism.CurCast.Cost) - } - case ret.DemonAndUndeadTargetCount >= 1 && ret.HolyWrath.IsReady(sim): - // Holy Wrath isn't worth casting if it will reduce usages of CS/DS - if ret.CrusaderStrike.ReadyAt()-sim.CurrentTime < 500*time.Millisecond { - break - } - if ret.DivineStorm.ReadyAt()-sim.CurrentTime < 500*time.Millisecond { - break - } - success := ret.HolyWrath.Cast(sim, target) - if !success { - ret.WaitForMana(sim, ret.HolyWrath.CurCast.Cost) - } - } - } - - // All possible next events - events := []time.Duration{ - nextSwingAt, - ret.GCD.ReadyAt(), - ret.SelectedJudgement.CD.ReadyAt(), - ret.DivineStorm.CD.ReadyAt(), - ret.HammerOfWrath.CD.ReadyAt(), - ret.HolyWrath.CD.ReadyAt(), - ret.CrusaderStrike.CD.ReadyAt(), - ret.Consecration.CD.ReadyAt(), - ret.Exorcism.CD.ReadyAt(), - ret.DivinePlea.CD.ReadyAt(), - } - - if ret.HandOfReckoning != nil { - events = append(events, ret.HandOfReckoning.CD.ReadyAt()) - } - - CancelChaosBane(ret, sim) - ret.waitUntilNextEvent(sim, events, ret.mainRotation) -} - -func (ret *RetributionPaladin) checkConsecrationClipping(sim *core.Simulation) bool { - if ret.AvoidClippingConsecration { - return ret.Consecration.AOEDot().TickLength*4 <= sim.GetRemainingDuration() - } else { - // If we're not configured to check, always return success. - return true - } -} - -// Helper function for finding the next event -func (ret *RetributionPaladin) waitUntilNextEvent(sim *core.Simulation, events []time.Duration, rotationCallback func(*core.Simulation)) { - - // Find the minimum possible next event that is greater than the current time - nextEventAt := time.Duration(math.MaxInt64) // any event will happen before forever. - for _, elem := range events { - if elem > sim.CurrentTime && elem < nextEventAt { - nextEventAt = elem - } - } - // If the next action is the GCD, just return - if nextEventAt == ret.GCD.ReadyAt() { - if ret.CancelChaosBane && ret.HasActiveAura("Chaos Bane") { - ret.GetAura("Chaos Bane").Deactivate(sim) - } - return - } - - // Otherwise add a pending action for the next time - pa := &core.PendingAction{ - Priority: core.ActionPriorityLow, - OnAction: rotationCallback, - NextActionAt: nextEventAt, - } - - sim.AddPendingAction(pa) - - if ret.CancelChaosBane && ret.HasActiveAura("Chaos Bane") { - ret.GetAura("Chaos Bane").Deactivate(sim) - } -} - -func CancelChaosBane(ret *RetributionPaladin, sim *core.Simulation) { - if !ret.Paladin.CancelChaosBane { - return - } - if a := ret.Paladin.GetAura("Chaos Bane"); a != nil { - a.Deactivate(sim) - } -} From 9a02ebe72aeb2da07a006276e886a21ac9360e1c Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 15:17:44 -0500 Subject: [PATCH 09/26] update priest APL only --- sim/priest/healing/healing_priest.go | 34 +------ sim/priest/healing/healing_priest_test.go | 2 - sim/priest/healing/rotation.go | 115 ---------------------- sim/priest/mind_flay.go | 8 +- sim/priest/mind_sear.go | 4 +- sim/priest/penance.go | 3 +- sim/priest/shadow/rotation.go | 9 -- sim/priest/shadow/shadow_priest.go | 9 -- sim/priest/shadow/shadow_priest_test.go | 3 - 9 files changed, 6 insertions(+), 181 deletions(-) delete mode 100644 sim/priest/healing/rotation.go delete mode 100644 sim/priest/shadow/rotation.go diff --git a/sim/priest/healing/healing_priest.go b/sim/priest/healing/healing_priest.go index d6f1307e12..1bdddd7cc2 100644 --- a/sim/priest/healing/healing_priest.go +++ b/sim/priest/healing/healing_priest.go @@ -1,7 +1,6 @@ package healing import ( - "github.com/wowsims/sod/sim/common" "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/priest" @@ -27,13 +26,7 @@ func RegisterHealingPriest() { type HealingPriest struct { *priest.Priest - rotation *proto.HealingPriest_Rotation - CustomRotation *common.CustomRotation - Options *proto.HealingPriest_Options - - // Spells to rotate through for cyclic rotation. - spellCycle []*core.Spell - nextCycleIndex int + Options *proto.HealingPriest_Options } func NewHealingPriest(character *core.Character, options *proto.Player) *HealingPriest { @@ -41,13 +34,10 @@ func NewHealingPriest(character *core.Character, options *proto.Player) *Healing basePriest := priest.New(character, options.TalentsString) hpriest := &HealingPriest{ - Priest: basePriest, - rotation: healingOptions.Rotation, - Options: healingOptions.Options, + Priest: basePriest, + Options: healingOptions.Options, } - hpriest.EnableResumeAfterManaWait(hpriest.tryUseGCD) - return hpriest } @@ -68,26 +58,8 @@ func (hpriest *HealingPriest) Initialize() { hpriest.CurrentTarget = hpriest.GetMainTarget() hpriest.Priest.Initialize() hpriest.Priest.RegisterHealingSpells() - - if hpriest.rotation.Type == proto.HealingPriest_Rotation_Custom { - hpriest.CustomRotation = hpriest.makeCustomRotation() - } - - if hpriest.CustomRotation == nil { - hpriest.rotation.Type = proto.HealingPriest_Rotation_Cycle - hpriest.spellCycle = []*core.Spell{ - hpriest.GreaterHeal, - hpriest.FlashHeal, - hpriest.CircleOfHealing, - hpriest.BindingHeal, - hpriest.PrayerOfHealing, - hpriest.PrayerOfMending, - hpriest.PenanceHeal, - } - } } func (hpriest *HealingPriest) Reset(sim *core.Simulation) { hpriest.Priest.Reset(sim) - hpriest.nextCycleIndex = 0 } diff --git a/sim/priest/healing/healing_priest_test.go b/sim/priest/healing/healing_priest_test.go index 2a2cddeeac..2beb396ec4 100644 --- a/sim/priest/healing/healing_priest_test.go +++ b/sim/priest/healing/healing_priest_test.go @@ -87,7 +87,6 @@ var PlayerOptionsDisc = &proto.Player_HealingPriest{ UseShadowfiend: true, RapturesPerMinute: 5, }, - Rotation: &proto.HealingPriest_Rotation{}, }, } @@ -97,6 +96,5 @@ var PlayerOptionsHoly = &proto.Player_HealingPriest{ UseInnerFire: true, UseShadowfiend: true, }, - Rotation: &proto.HealingPriest_Rotation{}, }, } diff --git a/sim/priest/healing/rotation.go b/sim/priest/healing/rotation.go deleted file mode 100644 index d35bf60778..0000000000 --- a/sim/priest/healing/rotation.go +++ /dev/null @@ -1,115 +0,0 @@ -package healing - -import ( - "github.com/wowsims/sod/sim/common" - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (hpriest *HealingPriest) OnGCDReady(sim *core.Simulation) { - hpriest.tryUseGCD(sim) -} - -func (hpriest *HealingPriest) tryUseGCD(sim *core.Simulation) { - if hpriest.CustomRotation != nil { - hpriest.CustomRotation.Cast(sim) - } else { - spell := hpriest.chooseSpell(sim) - - if success := spell.Cast(sim, hpriest.CurrentTarget); !success { - hpriest.WaitForMana(sim, spell.CurCast.Cost) - } - } -} - -func (hpriest *HealingPriest) chooseSpell(sim *core.Simulation) *core.Spell { - if !hpriest.Renew.CurHot().IsActive() { - return hpriest.Renew - } else if hpriest.PowerWordShield.CanCast(sim, hpriest.CurrentTarget) { - return hpriest.PowerWordShield - } else { - for !hpriest.spellCycle[hpriest.nextCycleIndex].IsReady(sim) { - hpriest.nextCycleIndex = (hpriest.nextCycleIndex + 1) % len(hpriest.spellCycle) - } - spell := hpriest.spellCycle[hpriest.nextCycleIndex] - hpriest.nextCycleIndex = (hpriest.nextCycleIndex + 1) % len(hpriest.spellCycle) - return spell - } -} - -func (hpriest *HealingPriest) makeCustomRotation() *common.CustomRotation { - return common.NewCustomRotation(hpriest.rotation.CustomRotation, hpriest.GetCharacter(), map[int32]common.CustomSpell{ - int32(proto.HealingPriest_Rotation_GreaterHeal): { - Spell: hpriest.GreaterHeal, - }, - int32(proto.HealingPriest_Rotation_FlashHeal): { - Spell: hpriest.FlashHeal, - }, - int32(proto.HealingPriest_Rotation_Renew): { - Spell: hpriest.Renew, - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - for _, unit := range hpriest.Env.Raid.AllUnits { - renewHot := hpriest.Renew.Hot(unit) - if renewHot != nil && !renewHot.IsActive() { - success := hpriest.Renew.Cast(sim, unit) - return success, hpriest.Renew.CurCast.Cost - } - } - panic("No valid Renew target") - }, - Condition: func(sim *core.Simulation) bool { - for _, unit := range hpriest.Env.Raid.AllUnits { - renewHot := hpriest.Renew.Hot(unit) - if renewHot != nil && !renewHot.IsActive() { - return true - } - } - return false - }, - }, - int32(proto.HealingPriest_Rotation_PowerWordShield): { - Spell: hpriest.PowerWordShield, - Action: func(sim *core.Simulation, target *core.Unit) (bool, float64) { - for _, unit := range hpriest.Env.Raid.AllUnits { - if hpriest.PowerWordShield.CanCast(sim, unit) { - success := hpriest.PowerWordShield.Cast(sim, unit) - return success, hpriest.PowerWordShield.CurCast.Cost - } - } - panic("No valid PowerWordShield target") - }, - Condition: func(sim *core.Simulation) bool { - for _, unit := range hpriest.Env.Raid.AllUnits { - if hpriest.PowerWordShield.CanCast(sim, unit) { - return true - } - } - return false - }, - }, - int32(proto.HealingPriest_Rotation_CircleOfHealing): { - Spell: hpriest.CircleOfHealing, - Condition: func(sim *core.Simulation) bool { - return hpriest.CircleOfHealing.IsReady(sim) - }, - }, - int32(proto.HealingPriest_Rotation_PrayerOfHealing): { - Spell: hpriest.PrayerOfHealing, - }, - int32(proto.HealingPriest_Rotation_PrayerOfMending): { - Spell: hpriest.PrayerOfMending, - Condition: func(sim *core.Simulation) bool { - return hpriest.PrayerOfMending.IsReady(sim) - }, - }, - int32(proto.HealingPriest_Rotation_Penance): { - Spell: hpriest.PenanceHeal, - Condition: func(sim *core.Simulation) bool { - return hpriest.PenanceHeal.IsReady(sim) - }, - }, - int32(proto.HealingPriest_Rotation_BindingHeal): { - Spell: hpriest.BindingHeal, - }, - }) -} diff --git a/sim/priest/mind_flay.go b/sim/priest/mind_flay.go index 5e5890e066..87ff0cd282 100644 --- a/sim/priest/mind_flay.go +++ b/sim/priest/mind_flay.go @@ -42,7 +42,6 @@ func (priest *Priest) getMindFlaySpellConfig(rank int) core.SpellConfig { level := [7]int{0, 20, 28, 36, 44, 52, 60}[rank] tickLength := time.Second - channelTime := tickLength * time.Duration(numTicks) mindFlayTickSpell := priest.getMindFlayTickSpell(rank, numTicks, baseDamage) return core.SpellConfig{ @@ -56,8 +55,7 @@ func (priest *Priest) getMindFlaySpellConfig(rank int) core.SpellConfig { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: channelTime, + GCD: core.GCDDefault, }, }, BonusHitRating: float64(priest.Talents.ShadowFocus) * 2 * core.SpellHitRatingPerHitChance, @@ -93,10 +91,6 @@ func (priest *Priest) getMindFlaySpellConfig(rank int) core.SpellConfig { } } -func (priest *Priest) MindFlayTickDuration() time.Duration { - return priest.ApplyCastSpeed(time.Second) -} - func (priest *Priest) registerMindFlay() { if !priest.Talents.MindFlay { return diff --git a/sim/priest/mind_sear.go b/sim/priest/mind_sear.go index 050a8d23ff..6187d8d6c0 100644 --- a/sim/priest/mind_sear.go +++ b/sim/priest/mind_sear.go @@ -41,7 +41,6 @@ func (priest *Priest) newMindSearSpell(numTicksIdx int32) *core.Spell { flags |= core.SpellFlagAPL } - channelTime := time.Second * time.Duration(numTicks) mindSearTickSpell := priest.getMindSearTickSpell(numTicksIdx) config := priest.getMindSearBaseConfig() @@ -52,8 +51,7 @@ func (priest *Priest) newMindSearSpell(numTicksIdx int32) *core.Spell { } config.Cast = core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: channelTime, + GCD: core.GCDDefault, }, } config.Dot = core.DotConfig{ diff --git a/sim/priest/penance.go b/sim/priest/penance.go index 431979fc61..207afae0a8 100644 --- a/sim/priest/penance.go +++ b/sim/priest/penance.go @@ -48,8 +48,7 @@ func (priest *Priest) makePenanceSpell(isHeal bool) *core.Spell { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: time.Second * 2, + GCD: core.GCDDefault, }, CD: core.Cooldown{ Timer: priest.NewTimer(), diff --git a/sim/priest/shadow/rotation.go b/sim/priest/shadow/rotation.go deleted file mode 100644 index fbc0d2c227..0000000000 --- a/sim/priest/shadow/rotation.go +++ /dev/null @@ -1,9 +0,0 @@ -package shadow - -import ( - "github.com/wowsims/sod/sim/core" -) - -func (spriest *ShadowPriest) OnGCDReady(sim *core.Simulation) { - spriest.DoNothing() -} diff --git a/sim/priest/shadow/shadow_priest.go b/sim/priest/shadow/shadow_priest.go index 478af95e13..f3842da75a 100644 --- a/sim/priest/shadow/shadow_priest.go +++ b/sim/priest/shadow/shadow_priest.go @@ -57,13 +57,4 @@ func (spriest *ShadowPriest) Initialize() { func (spriest *ShadowPriest) Reset(sim *core.Simulation) { spriest.Priest.Reset(sim) - - // Save info related to blood lust timing - spriest.BLUsedAt = 0 - if bloodlustMCD := spriest.GetMajorCooldownIgnoreTag(core.BloodlustActionID); bloodlustMCD != nil { - timings := bloodlustMCD.GetTimings() - if len(timings) > 0 { - spriest.BLUsedAt = timings[0] - } - } } diff --git a/sim/priest/shadow/shadow_priest_test.go b/sim/priest/shadow/shadow_priest_test.go index 71198483e9..66024caacb 100644 --- a/sim/priest/shadow/shadow_priest_test.go +++ b/sim/priest/shadow/shadow_priest_test.go @@ -56,8 +56,5 @@ var PlayerOptionsBasic = &proto.Player_ShadowPriest{ Options: &proto.ShadowPriest_Options{ Armor: proto.ShadowPriest_Options_InnerFire, }, - Rotation: &proto.ShadowPriest_Rotation{ - RotationType: proto.ShadowPriest_Rotation_Basic, - }, }, } From 29449d94ee74f9d1aa2484917eb5a13dec6ab6ff Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 15:32:57 -0500 Subject: [PATCH 10/26] update rogue APL only --- sim/_rogue/feint.go | 13 - sim/_rogue/rogue.go | 52 +--- sim/_rogue/rogue_test.go | 36 +-- sim/_rogue/rotation.go | 77 ----- sim/_rogue/rotation_assassination.go | 374 ----------------------- sim/_rogue/rotation_combat.go | 435 --------------------------- sim/_rogue/rotation_generic.go | 412 ------------------------- sim/_rogue/rotation_multi.go | 405 ------------------------- sim/_rogue/rotation_subtlety.go | 340 --------------------- sim/_rogue/talents.go | 11 - sim/_rogue/tricks_of_the_trade.go | 17 -- sim/_rogue/vanish.go | 26 -- 12 files changed, 15 insertions(+), 2183 deletions(-) delete mode 100644 sim/_rogue/rotation.go delete mode 100644 sim/_rogue/rotation_assassination.go delete mode 100644 sim/_rogue/rotation_combat.go delete mode 100644 sim/_rogue/rotation_generic.go delete mode 100644 sim/_rogue/rotation_multi.go delete mode 100644 sim/_rogue/rotation_subtlety.go diff --git a/sim/_rogue/feint.go b/sim/_rogue/feint.go index e3f2f3c399..4e47d5f610 100644 --- a/sim/_rogue/feint.go +++ b/sim/_rogue/feint.go @@ -35,17 +35,4 @@ func (rogue *Rogue) registerFeintSpell() { spell.CalcAndDealOutcome(sim, target, spell.OutcomeMeleeSpecialHit) }, }) - // Feint - if rogue.Rotation.UseFeint { - rogue.AddMajorCooldown(core.MajorCooldown{ - Spell: rogue.Feint, - Priority: core.CooldownPriorityDefault, - Type: core.CooldownTypeDPS, - //don't feint if you're gonna waste energy by using the gcd - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - thresh := 55.0 //55 simmed best with standard settings for now 3/12/2023, will refine with the rotational refinements. 55 was definitely best for combat, didn't make a difference for muti - return rogue.CurrentEnergy() <= thresh - }, - }) - } } diff --git a/sim/_rogue/rogue.go b/sim/_rogue/rogue.go index 621339d7d0..f75d14e879 100644 --- a/sim/_rogue/rogue.go +++ b/sim/_rogue/rogue.go @@ -1,7 +1,6 @@ package rogue import ( - "math" "time" "github.com/wowsims/sod/sim/core" @@ -39,21 +38,14 @@ const RogueBleedTag = "RogueBleed" type Rogue struct { core.Character - Talents *proto.RogueTalents - Options *proto.Rogue_Options - Rotation *proto.Rogue_Rotation - - rotation rotation + Talents *proto.RogueTalents + Options *proto.Rogue_Options bleedCategory *core.ExclusiveCategory sliceAndDiceDurations [6]time.Duration exposeArmorDurations [6]time.Duration - allMCDsDisabled bool - - maxEnergy float64 - Backstab *core.Spell BladeFlurry *core.Spell DeadlyPoison *core.Spell @@ -177,13 +169,6 @@ func (rogue *Rogue) Initialize() { rogue.registerVanishSpell() rogue.finishingMoveEffectApplier = rogue.makeFinishingMoveEffectApplier() - - if !rogue.IsUsingAPL && rogue.Rotation.TricksOfTheTradeFrequency != proto.Rogue_Rotation_Never && !rogue.HasSetBonus(Tier10, 2) { - rogue.RegisterPrepullAction(-10*time.Second, func(sim *core.Simulation) { - rogue.TricksOfTheTrade.Cast(sim, nil) - rogue.UpdateMajorCooldowns() - }) - } } func (rogue *Rogue) ApplyEnergyTickMultiplier(multiplier float64) { @@ -194,30 +179,6 @@ func (rogue *Rogue) Reset(sim *core.Simulation) { for _, mcd := range rogue.GetMajorCooldowns() { mcd.Disable() } - rogue.allMCDsDisabled = true - - if !rogue.IsUsingAPL { - // Stealth triggered effects (Overkill and Master of Subtlety) pre-pull activation - if rogue.Rotation.OpenWithGarrote || rogue.Rotation.OpenWithPremeditation { - rogue.AutoAttacks.CancelAutoSwing(sim) - rogue.StealthAura.Activate(sim) - } else { - if rogue.Options.StartingOverkillDuration > 0 { - if rogue.Talents.Overkill { - duration := time.Second * time.Duration(math.Min(float64(rogue.Options.StartingOverkillDuration), 20)) - rogue.OverkillAura.Activate(sim) - rogue.OverkillAura.UpdateExpires(duration) - } - if rogue.Talents.MasterOfSubtlety > 0 { - duration := time.Second * time.Duration(math.Min(float64(rogue.Options.StartingOverkillDuration), 6)) - rogue.MasterOfSubtletyAura.Activate(sim) - rogue.MasterOfSubtletyAura.UpdateExpires(duration) - } - } - } - } - - rogue.setupRotation(sim) } func (rogue *Rogue) MeleeCritMultiplier(applyLethality bool) float64 { @@ -240,7 +201,6 @@ func NewRogue(character *core.Character, options *proto.Player) *Rogue { Character: *character, Talents: &proto.RogueTalents{}, Options: rogueOptions.Options, - Rotation: rogueOptions.Rotation, } core.FillTalentsProto(rogue.Talents.ProtoReflect(), options.TalentsString, TalentTreeSizes) @@ -254,8 +214,7 @@ func NewRogue(character *core.Character, options *proto.Player) *Rogue { if rogue.HasSetBonus(Arena, 4) { maxEnergy += 10 } - rogue.maxEnergy = maxEnergy - rogue.EnableEnergyBar(maxEnergy, rogue.OnEnergyGain) + rogue.EnableEnergyBar(maxEnergy) rogue.ApplyEnergyTickMultiplier([]float64{0, 0.08, 0.16, 0.25}[rogue.Talents.Vitality]) rogue.EnableAutoAttacks(rogue, core.AutoAttackOptions{ @@ -292,11 +251,6 @@ func (rogue *Rogue) BreakStealth(sim *core.Simulation) { } } -// Can the rogue fulfil the weapon equipped requirement for Mutilate? -func (rogue *Rogue) CanMutilate() bool { - return rogue.Talents.Mutilate && rogue.HasDagger(core.MainHand) && rogue.HasDagger(core.OffHand) -} - // Does the rogue have a dagger equipped in the specified hand (main or offhand)? func (rogue *Rogue) HasDagger(hand core.Hand) bool { if hand == core.MainHand { diff --git a/sim/_rogue/rogue_test.go b/sim/_rogue/rogue_test.go index c86882133e..506b58ff49 100644 --- a/sim/_rogue/rogue_test.go +++ b/sim/_rogue/rogue_test.go @@ -218,79 +218,67 @@ var SubtletyTalents = "30532000235--512003203032012135011503113" var PlayerOptionsCombatDI = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyInstant, }, } var PlayerOptionsCombatDD = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyDeadly, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyDeadly, }, } var PlayerOptionsCombatID = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: InstantDeadly, - Rotation: &proto.Rogue_Rotation{}, + Options: InstantDeadly, }, } var PlayerOptionsCombatII = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: InstantInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: InstantInstant, }, } var PlayerOptionsNoLethality = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyInstant, }, } var PlayerOptionsNoPotW = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyInstant, }, } var PlayerOptionsNoLethalityNoPotW = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyInstant, }, } var PlayerOptionsAssassinationDI = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyInstant, }, } var PlayerOptionsAssassinationDD = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: DeadlyDeadly, - Rotation: &proto.Rogue_Rotation{}, + Options: DeadlyDeadly, }, } var PlayerOptionsAssassinationID = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: InstantDeadly, - Rotation: &proto.Rogue_Rotation{}, + Options: InstantDeadly, }, } var PlayerOptionsAssassinationII = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: InstantInstant, - Rotation: &proto.Rogue_Rotation{}, + Options: InstantInstant, }, } var PlayerOptionsSubtletyID = &proto.Player_Rogue{ Rogue: &proto.Rogue{ - Options: InstantDeadly, - Rotation: &proto.Rogue_Rotation{}, + Options: InstantDeadly, }, } diff --git a/sim/_rogue/rotation.go b/sim/_rogue/rotation.go deleted file mode 100644 index 517e1a1d4e..0000000000 --- a/sim/_rogue/rotation.go +++ /dev/null @@ -1,77 +0,0 @@ -package rogue - -import ( - "github.com/wowsims/sod/sim/core" -) - -type rotation interface { - setup(sim *core.Simulation, rogue *Rogue) - run(sim *core.Simulation, rogue *Rogue) -} - -type PriorityAction int32 - -const ( - Skip PriorityAction = iota - Build - Cast - Wait - Once -) - -type prio struct { - check func(sim *core.Simulation, rogue *Rogue) PriorityAction - cast func(sim *core.Simulation, rogue *Rogue) bool - cost float64 -} - -func (rogue *Rogue) OnEnergyGain(sim *core.Simulation) { - if rogue.IsUsingAPL { - return - } - - if sim.CurrentTime < 0 { - return - } - - rogue.TryUseCooldowns(sim) - - if !rogue.GCD.IsReady(sim) { - return - } - - rogue.rotation.run(sim, rogue) -} - -func (rogue *Rogue) OnGCDReady(sim *core.Simulation) { - if rogue.IsUsingAPL { - return - } - rogue.TryUseCooldowns(sim) - - if rogue.IsWaitingForEnergy() { - rogue.DoNothing() - return - } - - rogue.rotation.run(sim, rogue) -} - -func (rogue *Rogue) setupRotation(sim *core.Simulation) { - if rogue.IsUsingAPL { - return - } - switch { - case rogue.Env.GetNumTargets() >= 3: - rogue.rotation = &rotation_multi{} // rotation multi will soon be removed - case rogue.CanMutilate(): - rogue.rotation = &rotation_assassination{} - case rogue.Talents.CombatPotency > 0: - rogue.rotation = &rotation_combat{} - case rogue.Talents.HonorAmongThieves > 0: - rogue.rotation = &rotation_subtlety{} - default: - rogue.rotation = &rotation_generic{} - } - rogue.rotation.setup(sim, rogue) -} diff --git a/sim/_rogue/rotation_assassination.go b/sim/_rogue/rotation_assassination.go deleted file mode 100644 index 7f13be8469..0000000000 --- a/sim/_rogue/rotation_assassination.go +++ /dev/null @@ -1,374 +0,0 @@ -package rogue - -import ( - "log" - "slices" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type rotation_assassination struct { - prios []prio -} - -func (x *rotation_assassination) setup(sim *core.Simulation, rogue *Rogue) { - rogue.bleedCategory = rogue.CurrentTarget.GetExclusiveEffectCategory(core.BleedEffectCategory) - - x.prios = x.prios[:0] - - mutiCost := rogue.Mutilate.DefaultCast.Cost - rupCost := rogue.Rupture.DefaultCast.Cost - envCost := rogue.Envenom.DefaultCast.Cost - - // estimate of energy per second while nothing is cast - energyPerSecond := func() float64 { - if rogue.Talents.FocusedAttacks == 0 { - return 10 * rogue.EnergyTickMultiplier - } - - getCritChance := func(spell *core.Spell) float64 { - at := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - - critCap := 1.0 - at.BaseGlanceChance - if miss := at.BaseMissChance + 0.19 - spell.PhysicalHitChance(at); miss > 0 { - critCap -= miss - } - if dodge := at.BaseDodgeChance - spell.ExpertisePercentage() - rogue.PseudoStats.DodgeReduction; dodge > 0 { - critCap -= dodge - } - - critChance := spell.PhysicalCritChance(at) - if critChance > critCap { - critChance = critCap - } - return critChance - } - - critsPerSecond := getCritChance(rogue.AutoAttacks.MHAuto())/rogue.AutoAttacks.MainhandSwingSpeed().Seconds() + - getCritChance(rogue.AutoAttacks.OHAuto())/rogue.AutoAttacks.OffhandSwingSpeed().Seconds() - procChance := []float64{0, 0.33, 0.66, 1}[rogue.Talents.FocusedAttacks] - - return 10*rogue.EnergyTickMultiplier + critsPerSecond*procChance*2 - } - - // Garrote - if rogue.Rotation.OpenWithGarrote && !rogue.PseudoStats.InFrontOfTarget && rogue.IsStealthed() { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.CurrentEnergy() > rogue.Garrote.DefaultCast.Cost && rogue.IsStealthed() { - return Once - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Garrote.Cast(sim, rogue.CurrentTarget) - }, - rogue.Garrote.DefaultCast.Cost, - }) - } - - // Slice And Dice - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.SliceAndDiceAura.IsActive() { - return Skip - } - if rogue.ComboPoints() > 0 && rogue.CurrentEnergy() > rogue.SliceAndDice.DefaultCast.Cost { - return Cast - } - if rogue.ComboPoints() < 1 && rogue.CurrentEnergy() > mutiCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - }, - rogue.SliceAndDice.DefaultCast.Cost, - }) - - // Hunger while planning - if rogue.Talents.HungerForBlood { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - - prioExpose := rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once || - rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain - if prioExpose && !rogue.ExposeArmorAuras.Get(rogue.CurrentTarget).IsActive() { - return Skip - } - - if rogue.HungerForBloodAura.IsActive() { - return Skip - } - - if !x.targetHasBleed(sim, rogue) { - return Skip - } - - if x.targetHasBleed(sim, rogue) && rogue.CurrentEnergy() > rogue.HungerForBlood.DefaultCast.Cost { - return Cast - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.HungerForBlood.Cast(sim, rogue.CurrentTarget) - }, - rogue.HungerForBlood.DefaultCast.Cost, - }) - } - - // Expose armor - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once || rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain { - hasCastExpose := false - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if hasCastExpose && rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - return Skip - } - timeLeft := rogue.ExposeArmorAuras.Get(rogue.CurrentTarget).RemainingDuration(sim) - minPoints := max(1, min(rogue.Rotation.MinimumComboPointsExposeArmor, 5)) - if rogue.Rotation.ExposeArmorFrequency != proto.Rogue_Rotation_Once { - minPoints = 1 - } - if timeLeft <= 0 { - if rogue.ComboPoints() < minPoints { - if rogue.CurrentEnergy() >= mutiCost { - return Build - } else { - return Wait - } - } else { - if rogue.CurrentEnergy() >= rogue.ExposeArmor.DefaultCast.Cost { - return Cast - } else { - return Wait - } - } - } else { - energyGained := energyPerSecond() * timeLeft.Seconds() - cpGenerated := energyGained / mutiCost - currentCp := float64(rogue.ComboPoints()) - if currentCp+cpGenerated > 5 { - return Skip - } else { - if currentCp < 5 { - if rogue.CurrentEnergy() >= mutiCost { - return Build - } - } - return Wait - } - } - }, - func(sim *core.Simulation, rogue *Rogue) bool { - casted := rogue.ExposeArmor.Cast(sim, rogue.CurrentTarget) - if casted { - hasCastExpose = true - } - return casted - }, - rogue.ExposeArmor.DefaultCast.Cost, - }) - } - - // Rupture for Bleed - if rogue.Rotation.RuptureForBleed { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if x.targetHasBleed(sim, rogue) { - return Skip - } - if rogue.HungerForBloodAura.IsActive() { - return Skip - } - if rogue.ComboPoints() > 0 && rogue.CurrentEnergy() >= rupCost { - return Cast - } - if rogue.ComboPoints() < 1 && rogue.CurrentEnergy() >= mutiCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Rupture.Cast(sim, rogue.CurrentTarget) - }, - rupCost, - }) - } - - // Hunger for Blood - if rogue.Talents.HungerForBlood { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.HungerForBloodAura.IsActive() { - return Skip - } - - if !x.targetHasBleed(sim, rogue) { - return Skip - } - - if x.targetHasBleed(sim, rogue) && rogue.CurrentEnergy() >= rogue.HungerForBlood.DefaultCast.Cost { - return Cast - } - return Wait - }, - func(s *core.Simulation, r *Rogue) bool { - return rogue.HungerForBlood.Cast(sim, rogue.CurrentTarget) - }, - rogue.HungerForBlood.DefaultCast.Cost, - }) - } - - // Enable CDs - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - for _, mcd := range rogue.GetMajorCooldowns() { - if mcd.Spell != rogue.ColdBlood { - mcd.Enable() - } - } - return Once - }, - func(s *core.Simulation, r *Rogue) bool { - return true - }, - 0, - }) - - // Rupture - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - cp, e := rogue.ComboPoints(), rogue.CurrentEnergy() - - if rogue.Rotation.AssassinationFinisherPriority == proto.Rogue_Rotation_EnvenomRupture { - if rogue.Rupture.CurDot().IsActive() || sim.GetRemainingDuration() < rogue.RuptureDuration(4) { - return Skip - } - if !rogue.EnvenomAura.IsActive() || cp < 4 || rogue.Talents.Ruthlessness < 3 { - return Skip - } - - // use Rupture if you can re-cast Envenom with minimal delay, hoping for a Ruthlessness proc ;) - avail := e + rogue.EnvenomAura.RemainingDuration(sim).Seconds()*energyPerSecond() - cost := rupCost + mutiCost + envCost - if avail >= cost { - return Cast - } - return Skip - - } else { - if rogue.Rupture.CurDot().IsActive() || sim.GetRemainingDuration() < time.Second*18 { - return Skip - } - if cp >= 4 && e >= rupCost { - return Cast - } - if cp < 4 && e >= mutiCost { - return Build - } - return Wait - } - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Rupture.Cast(sim, rogue.CurrentTarget) - }, - rupCost, - }) - - // Envenom - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - e, cp := rogue.CurrentEnergy(), rogue.ComboPoints() - - // end of combat handling - possibly use low CP Envenoms instead of doing nothing - if dur := sim.GetRemainingDuration(); dur <= 10*time.Second { - avail := e + dur.Seconds()*energyPerSecond() - - if cp == 3 && avail < mutiCost+envCost && e >= envCost { - return Cast - } - - if cp >= 1 && avail < mutiCost && e >= envCost { - return Cast - } - } - - if cp >= 4 { - eps := energyPerSecond() - - if rogue.EnvenomAura.IsActive() { - // don't clip Envenom, unless you'd energy cap - if e < rogue.maxEnergy-eps && sim.GetRemainingDuration() >= rogue.EnvenomDuration(5) { - return Wait - } - return Cast - } - - // pool, so two Mutilate casts fit into the next uptime; this is a very minor DPS gain, and primarily for lower gear levels - cost := envCost + mutiCost + mutiCost - if cp == 5 && rogue.Talents.RelentlessStrikes == 5 { - cost -= 25 - } - avail := e + rogue.EnvenomDuration(cp).Seconds()*eps - if avail < cost { - return Wait - } - return Cast - } - - if e >= mutiCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - if rogue.ColdBlood.IsReady(sim) && rogue.ComboPoints() == 5 { - rogue.ColdBlood.Cast(sim, rogue.CurrentTarget) - } - return rogue.Envenom.Cast(sim, rogue.CurrentTarget) - }, - envCost, - }) -} - -func (x *rotation_assassination) run(sim *core.Simulation, rogue *Rogue) { - for i := 0; i < len(x.prios); i++ { - switch p := x.prios[i]; p.check(sim, rogue) { - case Skip: - continue - case Build: - if !rogue.Mutilate.Cast(sim, rogue.CurrentTarget) { - rogue.WaitForEnergy(sim, rogue.Mutilate.DefaultCast.Cost) - return - } - case Cast: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - case Once: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - x.prios = slices.Delete(x.prios, i, i+1) - i-- - case Wait: - rogue.DoNothing() - return - } - - if !rogue.GCD.IsReady(sim) { - return - } - } - log.Panic("skipped all prios") -} - -func (x *rotation_assassination) targetHasBleed(_ *core.Simulation, rogue *Rogue) bool { - return rogue.bleedCategory.AnyActive() || rogue.CurrentTarget.HasActiveAuraWithTag(RogueBleedTag) || rogue.Options.AssumeBleedActive -} diff --git a/sim/_rogue/rotation_combat.go b/sim/_rogue/rotation_combat.go deleted file mode 100644 index 073857e138..0000000000 --- a/sim/_rogue/rotation_combat.go +++ /dev/null @@ -1,435 +0,0 @@ -package rogue - -import ( - "log" - "slices" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type rotation_combat struct { - prios []prio - - builder *core.Spell -} - -func (x *rotation_combat) setup(_ *core.Simulation, rogue *Rogue) { - x.prios = x.prios[:0] - - x.builder = rogue.SinisterStrike - if rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_Backstab && rogue.HasDagger(core.MainHand) && !rogue.PseudoStats.InFrontOfTarget { - x.builder = rogue.Backstab - } - if rogue.Talents.Hemorrhage && rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_HemorrhageCombat { - x.builder = rogue.Hemorrhage - } - - bldCost := x.builder.DefaultCast.Cost - sndCost := rogue.SliceAndDice.DefaultCast.Cost - rupCost := rogue.Rupture.DefaultCast.Cost - evisCost := rogue.Eviscerate.DefaultCast.Cost - - baseEps := 10 * rogue.EnergyTickMultiplier - maxPool := rogue.maxEnergy - 3*float64(rogue.Talents.CombatPotency) - - ruthCp := 0.2 * float64(rogue.Talents.Ruthlessness) - - // estimate of energy per second while nothing is cast - energyPerSecond := func() float64 { - if rogue.Talents.CombatPotency == 0 { - return 10 * rogue.EnergyTickMultiplier - } - - spell := rogue.AutoAttacks.OHAuto() - at := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - - landChance := 1.0 - if miss := at.BaseMissChance + 0.19 - spell.PhysicalHitChance(at); miss > 0 { - landChance -= miss - } - if dodge := at.BaseDodgeChance - spell.ExpertisePercentage() - spell.Unit.PseudoStats.DodgeReduction; dodge > 0 { - landChance -= dodge - } - - landsPerSecond := landChance / rogue.AutoAttacks.OffhandSwingSpeed().Seconds() - - return 10*rogue.EnergyTickMultiplier + landsPerSecond*0.2*float64(rogue.Talents.CombatPotency)*3 - } - - rupRemaining := func(sim *core.Simulation) time.Duration { - if dot := rogue.Rupture.CurDot(); dot.IsActive() { - return dot.RemainingDuration(sim) - } - return 0 - } - - // Garrote - if rogue.Rotation.OpenWithGarrote && !rogue.PseudoStats.InFrontOfTarget && rogue.IsStealthed() { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.CurrentEnergy() > rogue.Garrote.DefaultCast.Cost && rogue.IsStealthed() { - return Once - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Garrote.Cast(sim, rogue.CurrentTarget) - }, - rogue.Garrote.DefaultCast.Cost, - }) - } - - // Slice And Dice - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - cp, e := rogue.ComboPoints(), rogue.CurrentEnergy() - - if sndDur := rogue.SliceAndDiceAura.RemainingDuration(sim); sndDur > 0 { - if cp == 5 { // pool for snd if pooling for rupture fails - rupDur := rupRemaining(sim) - if e+rupDur.Seconds()*energyPerSecond() > maxPool { - if e+sndDur.Seconds()*energyPerSecond() <= maxPool { - return Wait - } - } - return Skip - } - - if cp >= 1 { // don't build if it reduces uptime - if e+sndDur.Seconds()*energyPerSecond() < sndCost+bldCost || sndDur < time.Second { - return Wait - } - } - return Skip - } - - // end of fight - heuristically, 2s of snd beat a 3 CP eviscerate for DPE, and 3s are close to a 5 CP one. - if cp >= 3 && sim.GetRemainingDuration() < time.Duration(2000+600*cp)*time.Millisecond { - return Skip - } - - if cp >= 1 && e >= sndCost { - return Cast - } - if cp < 1 && e >= bldCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - }, - sndCost, - }) - - // Expose armor - update this as well - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once || rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain { - hasCastExpose := false - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if hasCastExpose && rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - return Skip - } - timeLeft := rogue.ExposeArmorAuras.Get(rogue.CurrentTarget).RemainingDuration(sim) - minPoints := max(1, min(rogue.Rotation.MinimumComboPointsExposeArmor, 5)) - if rogue.Rotation.ExposeArmorFrequency != proto.Rogue_Rotation_Once { - minPoints = 1 - } - if timeLeft <= 0 { - if rogue.ComboPoints() < minPoints { - if rogue.CurrentEnergy() >= bldCost { - return Build - } else { - return Wait - } - } else { - if rogue.CurrentEnergy() >= rogue.ExposeArmor.DefaultCast.Cost { - return Cast - } else { - return Wait - } - } - } else { - energyGained := energyPerSecond() * timeLeft.Seconds() - cpGenerated := energyGained / bldCost - currentCp := float64(rogue.ComboPoints()) - if currentCp+cpGenerated > 5 { - return Skip - } else { - if currentCp < 5 { - if rogue.CurrentEnergy() >= bldCost { - return Build - } - } - return Wait - } - } - }, - func(sim *core.Simulation, rogue *Rogue) bool { - casted := rogue.ExposeArmor.Cast(sim, rogue.CurrentTarget) - if casted { - hasCastExpose = true - } - return casted - }, - rogue.ExposeArmor.DefaultCast.Cost, - }) - } - - // Enable CDs - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - for _, mcd := range rogue.GetMajorCooldowns() { - mcd.Enable() - } - return Once - }, - func(s *core.Simulation, r *Rogue) bool { - return true - }, - 0, - }) - - if rogue.Rotation.CombatFinisherPriority == proto.Rogue_Rotation_EviscerateRupture { - // this is the pre-3.3.3 "rupture-less" rotation, essentially - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - e, cp := rogue.CurrentEnergy(), rogue.ComboPoints() - - if dur := sim.GetRemainingDuration(); dur <= 10*time.Second { - // end of fight handling - build towards a 3+ cp eviscerate, or just sinister strike - switch cp { - case 5: - if e >= evisCost { - return Cast - } - return Wait - default: - if e+dur.Seconds()*energyPerSecond() >= bldCost+evisCost { - return Build - } - if cp >= 3 && e >= evisCost { - return Cast - } - if cp < 3 && e >= bldCost { - return Build - } - } - return Wait - } - - if cp >= 5 { - sndDur := rogue.SliceAndDiceAura.RemainingDuration(sim) - // correcting with ruthCp seems to be a loss, so we just use bldCost directly - if e+sndDur.Seconds()*energyPerSecond() >= evisCost+bldCost+sndCost { - return Cast - } - return Wait - } - return Build - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Eviscerate.Cast(sim, rogue.CurrentTarget) - }, - evisCost, - }) - - return - } - - const ruptureMinDuration = time.Second * 10 // heuristically, 4-5 rupture ticks are better DPE than eviscerate - - // seconds a 5 cp rupture can be delayed to match a 4 cp rupture's dps. for rup4to5 and rup3to4, this delay is < 2s, - // which also means that clipping 3 or 4 cp ruptures is usually a dps loss - rup4to5 := rogue.RuptureDuration(4).Seconds() * (1 - rogue.RuptureDamage(4)/rogue.RuptureDamage(5)) - rup3to4 := rogue.RuptureDuration(3).Seconds() * (1 - rogue.RuptureDamage(3)/rogue.RuptureDamage(4)) - - // Rupture - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - cp, e := rogue.ComboPoints(), rogue.CurrentEnergy() - - if sim.GetRemainingDuration() < ruptureMinDuration { - return Skip - } - - rupDot := rogue.Rupture.CurDot() - if !rupDot.IsActive() { - if cp == 5 && e >= rupCost { - return Cast - } - if cp == 4 && e+rup4to5*energyPerSecond() < bldCost+rupCost { - return Cast - } - if cp == 3 && e+rup3to4*energyPerSecond() < bldCost+rupCost { - return Cast - } - if e >= bldCost { - return Build - } - return Wait - } - - // there's ample time to rebuild, simply skip - dur := rupRemaining(sim).Seconds() - if e+dur*baseEps > maxPool { - return Skip - } - - if cp == 5 { - if e+dur*energyPerSecond() > maxPool { - return Skip // can't pool any longer, maybe we can fit in Eviscerate - } - return Wait - } - if cp == 4 && e+(dur+rup4to5)*energyPerSecond() < bldCost+rupCost { - return Wait - } - if cp == 3 && e+(dur+rup3to4)*energyPerSecond() < bldCost+rupCost { - return Wait - } - if e >= bldCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Rupture.Cast(sim, rogue.CurrentTarget) - }, - rupCost, - }) - - bldPerCp := 1.0 - if x.builder == rogue.Backstab && rogue.Talents.SealFate > 0 { - attackTable := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - crit := rogue.Backstab.PhysicalCritChance(attackTable) - bldPerCp = 1 / (1 + crit*(0.2*float64(rogue.Talents.SealFate))) - } - - // Eviscerate - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - e, cp := rogue.CurrentEnergy(), rogue.ComboPoints() - - if dur := sim.GetRemainingDuration(); dur <= ruptureMinDuration { - // end of fight handling - build towards a 3+ cp eviscerate, or just sinister strike - switch cp { - case 5: - if e >= evisCost { - return Cast - } - return Wait - default: - if e+dur.Seconds()*energyPerSecond() >= bldCost+evisCost { - return Build - } - if cp >= 3 && e >= evisCost { - return Cast - } - if cp < 3 && e >= bldCost { - return Build - } - } - return Wait - } - - // we only get here if there's ample time left on rupture, or rupture pooling failed: in these cases, we - // can try to fill in a 5 cp eviscerate, if it's not too disruptive. lower cp eviscerates aren't worth it, - // since sinister spam isn't all that much worse - if cp <= 4 { - return Build - } - - cost := evisCost + (4-ruthCp)*bldCost*bldPerCp + rupCost - - rupDur := rupRemaining(sim) - sndDur := rogue.SliceAndDiceAura.RemainingDuration(sim) - if sndDur < rupDur { - cost += sndCost + (1-ruthCp)*bldCost*bldPerCp - } - - if avail := e + rupDur.Seconds()*energyPerSecond(); avail >= cost { - return Cast - } - - // we'd lose a CP here, so we just wait... - if e <= maxPool { - return Wait - } - - // ... and if that doesn't work, allow to clip snd - if sndDur < rogue.sliceAndDiceDurations[2]-rogue.sliceAndDiceDurations[1] { - rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - return Wait - } - - return Build - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Eviscerate.Cast(sim, rogue.CurrentTarget) - }, - evisCost, - }) -} - -func (x *rotation_combat) run(sim *core.Simulation, rogue *Rogue) { - for i := 0; i < len(x.prios); i++ { - switch p := x.prios[i]; p.check(sim, rogue) { - case Skip: - continue - case Build: - //Handle Ghostly Strike. This is badly copy-pasted code, and is not considered in a regular raid setting. - if rogue.Talents.GhostlyStrike && rogue.Rotation.UseGhostlyStrike && rogue.GhostlyStrike.IsReady(sim) { - x.builder = rogue.GhostlyStrike - - if !x.builder.Cast(sim, rogue.CurrentTarget) { - rogue.WaitForEnergy(sim, x.builder.DefaultCast.Cost) - - x.builder = rogue.SinisterStrike - if rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_Backstab && rogue.HasDagger(core.MainHand) && !rogue.PseudoStats.InFrontOfTarget { - x.builder = rogue.Backstab - } - if rogue.Talents.Hemorrhage && rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_HemorrhageCombat { - x.builder = rogue.Hemorrhage - } - - return - } - - x.builder = rogue.SinisterStrike - if rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_Backstab && rogue.HasDagger(core.MainHand) && !rogue.PseudoStats.InFrontOfTarget { - x.builder = rogue.Backstab - } - if rogue.Talents.Hemorrhage && rogue.Rotation.CombatBuilder == proto.Rogue_Rotation_HemorrhageCombat { - x.builder = rogue.Hemorrhage - } - //Done with Ghostly Strike - } else if !x.builder.Cast(sim, rogue.CurrentTarget) { - rogue.WaitForEnergy(sim, x.builder.DefaultCast.Cost) - return - } - case Cast: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - case Once: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - x.prios = slices.Delete(x.prios, i, i+1) - i-- - case Wait: - rogue.DoNothing() - return - } - - if !rogue.GCD.IsReady(sim) { - return - } - } - log.Panic("skipped all prios") -} diff --git a/sim/_rogue/rotation_generic.go b/sim/_rogue/rotation_generic.go deleted file mode 100644 index 3a99adf72a..0000000000 --- a/sim/_rogue/rotation_generic.go +++ /dev/null @@ -1,412 +0,0 @@ -package rogue - -import ( - "log" - "slices" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type rotation_generic struct { - prios []prio - - builder *core.Spell -} - -func (x *rotation_generic) setup(_ *core.Simulation, rogue *Rogue) { - x.prios = x.prios[:0] - - x.builder = rogue.SinisterStrike - if rogue.HasDagger(core.MainHand) && !rogue.PseudoStats.InFrontOfTarget { - x.builder = rogue.Backstab - } - if rogue.CanMutilate() { - x.builder = rogue.Mutilate - } - if rogue.Talents.Hemorrhage { - x.builder = rogue.Hemorrhage - } - - bldCost := x.builder.DefaultCast.Cost - sndCost := rogue.SliceAndDice.DefaultCast.Cost - rupCost := rogue.Rupture.DefaultCast.Cost - - baseEps := 10 * rogue.EnergyTickMultiplier - maxPool := rogue.maxEnergy - 3*float64(rogue.Talents.CombatPotency) - 2*float64(rogue.Talents.FocusedAttacks)/3.0 - - ruthCp := 0.2 * float64(rogue.Talents.Ruthlessness) - rsPerCp := float64(rogue.Talents.RelentlessStrikes) - - // estimate of energy per second while nothing is cast - energyPerSecond := func() float64 { - var eps float64 - if rogue.Talents.CombatPotency > 0 { - spell := rogue.AutoAttacks.OHAuto() - at := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - - landChance := 1.0 - if miss := at.BaseMissChance + 0.19 - spell.PhysicalHitChance(at); miss > 0 { - landChance -= miss - } - if dodge := at.BaseDodgeChance - spell.ExpertisePercentage() - spell.Unit.PseudoStats.DodgeReduction; dodge > 0 { - landChance -= dodge - } - - landsPerSecond := landChance / rogue.AutoAttacks.OffhandSwingSpeed().Seconds() - - eps += landsPerSecond * 0.2 * float64(rogue.Talents.CombatPotency) * 3 - } - if rogue.Talents.FocusedAttacks > 0 { - getCritChance := func(spell *core.Spell) float64 { - at := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - - critCap := 1.0 - at.BaseGlanceChance - if miss := at.BaseMissChance + 0.19 - spell.PhysicalHitChance(at); miss > 0 { - critCap -= miss - } - if dodge := at.BaseDodgeChance - spell.ExpertisePercentage() - rogue.PseudoStats.DodgeReduction; dodge > 0 { - critCap -= dodge - } - - critChance := spell.PhysicalCritChance(at) - if critChance > critCap { - critChance = critCap - } - return critChance - } - - critsPerSecond := getCritChance(rogue.AutoAttacks.MHAuto())/rogue.AutoAttacks.MainhandSwingSpeed().Seconds() + - getCritChance(rogue.AutoAttacks.OHAuto())/rogue.AutoAttacks.OffhandSwingSpeed().Seconds() - procChance := []float64{0, 0.33, 0.66, 1}[rogue.Talents.FocusedAttacks] - - eps += critsPerSecond * procChance * 2 - } - return 10*rogue.EnergyTickMultiplier + eps - } - - rupRemaining := func(sim *core.Simulation) time.Duration { - if dot := rogue.Rupture.CurDot(); dot.IsActive() { - return dot.RemainingDuration(sim) - } - return 0 - } - - // Garrote - if rogue.Rotation.OpenWithGarrote && !rogue.PseudoStats.InFrontOfTarget { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.CurrentEnergy() > rogue.Garrote.DefaultCast.Cost { - return Once - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Garrote.Cast(sim, rogue.CurrentTarget) - }, - rogue.Garrote.DefaultCast.Cost, - }) - } - - // Slice And Dice - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - cp, e := rogue.ComboPoints(), rogue.CurrentEnergy() - - if sndDur := rogue.SliceAndDiceAura.RemainingDuration(sim); sndDur > 0 { - if cp == 5 { // pool for snd if pooling for rupture fails - rupDur := rupRemaining(sim) - if e+rupDur.Seconds()*energyPerSecond() > maxPool { - if e+sndDur.Seconds()*energyPerSecond() <= maxPool { - return Wait - } - } - return Skip - } - - if cp >= 1 { // don't build if it reduces uptime - if e+sndDur.Seconds()*energyPerSecond() < sndCost+bldCost || sndDur < time.Second { - return Wait - } - } - return Skip - } - - // end of fight - heuristically, 2s of snd beat a 3 CP eviscerate for DPE, and 3s are close to a 5 CP one. - if cp >= 3 && sim.GetRemainingDuration() < time.Duration(2000+600*cp)*time.Millisecond { - return Skip - } - - if cp >= 1 && e >= sndCost { - return Cast - } - if cp < 1 && e >= bldCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - }, - sndCost, - }) - - // Expose armor - update this as well - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once || rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain { - hasCastExpose := false - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if hasCastExpose && rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - return Skip - } - timeLeft := rogue.ExposeArmorAuras.Get(rogue.CurrentTarget).RemainingDuration(sim) - minPoints := max(1, min(rogue.Rotation.MinimumComboPointsExposeArmor, 5)) - if rogue.Rotation.ExposeArmorFrequency != proto.Rogue_Rotation_Once { - minPoints = 1 - } - if timeLeft <= 0 { - if rogue.ComboPoints() < minPoints { - if rogue.CurrentEnergy() >= bldCost { - return Build - } else { - return Wait - } - } else { - if rogue.CurrentEnergy() >= rogue.ExposeArmor.DefaultCast.Cost { - return Cast - } else { - return Wait - } - } - } else { - energyGained := energyPerSecond() * timeLeft.Seconds() - cpGenerated := energyGained / bldCost - currentCp := float64(rogue.ComboPoints()) - if currentCp+cpGenerated > 5 { - return Skip - } else { - if currentCp < 5 { - if rogue.CurrentEnergy() >= bldCost { - return Build - } - } - return Wait - } - } - }, - func(sim *core.Simulation, rogue *Rogue) bool { - casted := rogue.ExposeArmor.Cast(sim, rogue.CurrentTarget) - if casted { - hasCastExpose = true - } - return casted - }, - rogue.ExposeArmor.DefaultCast.Cost, - }) - } - - // Enable CDs - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - for _, mcd := range rogue.GetMajorCooldowns() { - mcd.Enable() - } - return Once - }, - func(s *core.Simulation, r *Rogue) bool { - return true - }, - 0, - }) - - const ruptureMinDuration = time.Second * 10 // heuristically, 4-5 rupture ticks are better DPE than eviscerate - - // seconds a 5 cp rupture can be delayed to match a 4 cp rupture's dps. for rup4to5 and rup3to4, this delay is < 2s, - // which also means that clipping 3 or 4 cp ruptures is usually a dps loss - rup4to5 := rogue.RuptureDuration(4).Seconds() * (1 - rogue.RuptureDamage(4)/rogue.RuptureDamage(5)) - rup3to4 := rogue.RuptureDuration(3).Seconds() * (1 - rogue.RuptureDamage(3)/rogue.RuptureDamage(4)) - - // Rupture - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - cp, e := rogue.ComboPoints(), rogue.CurrentEnergy() - - if sim.GetRemainingDuration() < ruptureMinDuration { - return Skip - } - - rupDot := rogue.Rupture.CurDot() - if !rupDot.IsActive() { - if cp == 5 && e >= rupCost { - return Cast - } - if cp == 4 && e+rup4to5*energyPerSecond() < bldCost+rupCost { - return Cast - } - if cp == 3 && e+rup3to4*energyPerSecond() < bldCost+rupCost { - return Cast - } - if e >= bldCost { - return Build - } - return Wait - } - - // there's ample time to rebuild, simply skip - dur := rupRemaining(sim).Seconds() - if e+dur*baseEps > maxPool { - return Skip - } - - if cp == 5 { - if e+dur*energyPerSecond() > maxPool { - return Skip // can't pool any longer, maybe we can fit in Eviscerate - } - return Wait - } - if cp == 4 && e+(dur+rup4to5)*energyPerSecond() < bldCost+rupCost { - return Wait - } - if cp == 3 && e+(dur+rup3to4)*energyPerSecond() < bldCost+rupCost { - return Wait - } - if e >= bldCost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Rupture.Cast(sim, rogue.CurrentTarget) - }, - rupCost, - }) - - bldPerCp := 1.0 - if x.builder == rogue.SinisterStrike { - attackTable := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - crit := rogue.SinisterStrike.PhysicalCritChance(attackTable) - bldPerCp = 1 / (1 + crit*(0.2*float64(rogue.Talents.SealFate))) - } - if x.builder == rogue.Backstab { - attackTable := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - crit := rogue.Backstab.PhysicalCritChance(attackTable) - bldPerCp = 1 / (1 + crit*(0.2*float64(rogue.Talents.SealFate))) - } - if x.builder == rogue.Hemorrhage { - attackTable := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - crit := rogue.Hemorrhage.PhysicalCritChance(attackTable) - bldPerCp = 1 / (1 + crit*(0.2*float64(rogue.Talents.SealFate))) - } - if x.builder == rogue.Mutilate { - attackTable := rogue.AttackTables[rogue.CurrentTarget.UnitIndex] - critMH := rogue.MutilateMH.PhysicalCritChance(attackTable) - critOH := rogue.MutilateOH.PhysicalCritChance(attackTable) - crit := 1 - (1-critMH)*(1-critOH) - bldPerCp = 1 / (2 + crit*(0.2*float64(rogue.Talents.SealFate))) - } - - // direct damage finisher (Eviscerate/Envenom) - finisher := rogue.Eviscerate - if rogue.Talents.MasterPoisoner > 0 { - finisher = rogue.Envenom - } - finisherCost := finisher.DefaultCast.Cost - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - e, cp := rogue.CurrentEnergy(), rogue.ComboPoints() - - if dur := sim.GetRemainingDuration(); dur <= ruptureMinDuration { - // end of fight handling - build towards a 3+ cp finisher, or just spam the builder - switch cp { - case 5: - if e >= finisherCost { - return Cast - } - return Wait - default: - if e+dur.Seconds()*energyPerSecond() >= bldCost+finisherCost { - return Build - } - if cp >= 3 && e >= finisherCost { - return Cast - } - if cp < 3 && e >= bldCost { - return Build - } - } - return Wait - } - - // we only get here if there's ample time left on rupture, or rupture pooling failed: in these cases, we - // can try to fill in a 5 cp finisher, if it's not too disruptive. lower cp finishers aren't worth it, - // since builder spam isn't all that much worse - if cp <= 4 { - return Build - } - - cost := finisherCost - 5*rsPerCp + (4-ruthCp)*bldCost*bldPerCp + rupCost - - rupDur := rupRemaining(sim) - sndDur := rogue.SliceAndDiceAura.RemainingDuration(sim) - if sndDur < rupDur { - cost += sndCost - 1*rsPerCp + (1-ruthCp)*bldCost*bldPerCp - } - - if avail := e + rupDur.Seconds()*energyPerSecond(); avail >= cost { - return Cast - } - - // we'd lose a CP here, so we just wait... - if e <= maxPool { - return Wait - } - - // ... and if that doesn't work, allow to clip snd - if sndDur < rogue.sliceAndDiceDurations[2]-rogue.sliceAndDiceDurations[1] { - rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - return Wait - } - - return Build - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return finisher.Cast(sim, rogue.CurrentTarget) - }, - finisherCost, - }) -} - -func (x *rotation_generic) run(sim *core.Simulation, rogue *Rogue) { - for i := 0; i < len(x.prios); i++ { - switch p := x.prios[i]; p.check(sim, rogue) { - case Skip: - continue - case Build: - if !x.builder.Cast(sim, rogue.CurrentTarget) { - rogue.WaitForEnergy(sim, x.builder.DefaultCast.Cost) - return - } - case Cast: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - case Once: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - x.prios = slices.Delete(x.prios, i, i+1) - i-- - case Wait: - rogue.DoNothing() - return - } - - if !rogue.GCD.IsReady(sim) { - return - } - } - log.Panic("skipped all prios") -} diff --git a/sim/_rogue/rotation_multi.go b/sim/_rogue/rotation_multi.go deleted file mode 100644 index cc5cc83b75..0000000000 --- a/sim/_rogue/rotation_multi.go +++ /dev/null @@ -1,405 +0,0 @@ -package rogue - -import ( - "math" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type rogueRotationItem struct { - ExpiresAt time.Duration - MinimumBuildDuration time.Duration - MaximumBuildDuration time.Duration - PrioIndex int -} - -type roguePriorityItem struct { - Aura *core.Aura - CastCount int32 - EnergyCost float64 - PoolAmount float64 - GetDuration func(*Rogue, int32) time.Duration - GetSpell func(*Rogue, int32) *core.Spell - IsFiller bool - MaximumComboPoints int32 - MaxCasts int32 - MinimumComboPoints int32 -} - -type shouldCastRotationItemResult int32 - -const ( - ShouldNotCast shouldCastRotationItemResult = iota - ShouldBuild - ShouldCast - ShouldWait -) - -type rotation_multi struct { - priorityItems []roguePriorityItem - rotationItems []rogueRotationItem - - builder *core.Spell - builderPoints int32 -} - -func (x *rotation_multi) setup(sim *core.Simulation, rogue *Rogue) { - x.builder = rogue.SinisterStrike - x.builderPoints = 1 - - if rogue.CanMutilate() { - x.builder = rogue.Mutilate - x.builderPoints = 2 - } - - if rogue.Talents.Hemorrhage { - x.builder = rogue.Hemorrhage - x.builderPoints = 1 - } - - if rogue.Talents.SlaughterFromTheShadows > 0 && !rogue.Rotation.HemoWithDagger && !rogue.PseudoStats.InFrontOfTarget && rogue.HasDagger(core.MainHand) { - x.builder = rogue.Backstab - x.builderPoints = 1 - } - - // Slice and Dice - x.priorityItems = x.priorityItems[:0] - - sliceAndDice := roguePriorityItem{ - MinimumComboPoints: 1, - MaximumComboPoints: 5, - Aura: rogue.SliceAndDiceAura, - EnergyCost: rogue.SliceAndDice.DefaultCast.Cost, - GetDuration: func(rogue *Rogue, cp int32) time.Duration { - return rogue.sliceAndDiceDurations[cp] - }, - GetSpell: func(rogue *Rogue, cp int32) *core.Spell { - return rogue.SliceAndDice - }, - } - if rogue.Rotation.MultiTargetSliceFrequency != proto.Rogue_Rotation_Never { - sliceAndDice.MinimumComboPoints = max(1, rogue.Rotation.MinimumComboPointsMultiTargetSlice) - if rogue.Rotation.MultiTargetSliceFrequency == proto.Rogue_Rotation_Once { - sliceAndDice.MaxCasts = 1 - } - x.priorityItems = append(x.priorityItems, sliceAndDice) - } - - // Expose Armor - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain || - rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - minPoints := int32(1) - maxCasts := int32(0) - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - minPoints = rogue.Rotation.MinimumComboPointsExposeArmor - maxCasts = 1 - } - x.priorityItems = append(x.priorityItems, roguePriorityItem{ - MaxCasts: maxCasts, - MaximumComboPoints: 5, - MinimumComboPoints: minPoints, - Aura: rogue.ExposeArmorAuras.Get(rogue.CurrentTarget), - EnergyCost: rogue.ExposeArmor.DefaultCast.Cost, - GetDuration: func(rogue *Rogue, cp int32) time.Duration { - return rogue.exposeArmorDurations[cp] - }, - GetSpell: func(rogue *Rogue, cp int32) *core.Spell { - return rogue.ExposeArmor - }, - }) - } - - // Hunger for Blood - if rogue.Talents.HungerForBlood { - x.priorityItems = append(x.priorityItems, roguePriorityItem{ - MaximumComboPoints: 0, - Aura: rogue.HungerForBloodAura, - EnergyCost: rogue.HungerForBlood.DefaultCast.Cost, - GetDuration: func(rogue *Rogue, cp int32) time.Duration { - return rogue.HungerForBloodAura.Duration - }, - GetSpell: func(rogue *Rogue, cp int32) *core.Spell { - return rogue.HungerForBlood - }, - }) - } - - // Dummy priority to enable CDs - x.priorityItems = append(x.priorityItems, roguePriorityItem{ - MaxCasts: 1, - MaximumComboPoints: 0, - GetDuration: func(rogue *Rogue, cp int32) time.Duration { - return 0 - }, - GetSpell: func(rogue *Rogue, cp int32) *core.Spell { - if rogue.allMCDsDisabled { - for _, mcd := range rogue.GetMajorCooldowns() { - mcd.Enable() - } - rogue.allMCDsDisabled = false - } - return nil - }, - }) - - x.priorityItems = append(x.priorityItems, roguePriorityItem{ - MaximumComboPoints: 0, - EnergyCost: rogue.FanOfKnives.DefaultCast.Cost, - GetSpell: func(rogue *Rogue, i int32) *core.Spell { - return rogue.FanOfKnives - }, - }) - x.rotationItems = x.planRotation(sim, rogue) -} - -func (x *rotation_multi) run(sim *core.Simulation, rogue *Rogue) { - if len(x.rotationItems) < 1 { - panic("Rotation is empty") - } - eps := x.getExpectedEnergyPerSecond(rogue) - shouldCast := x.shouldCastNextRotationItem(sim, rogue, eps) - item := x.rotationItems[0] - prio := x.priorityItems[item.PrioIndex] - - switch shouldCast { - case ShouldNotCast: - x.rotationItems = x.rotationItems[1:] - x.run(sim, rogue) - case ShouldBuild: - spell := x.builder - if spell == nil || spell.Cast(sim, rogue.CurrentTarget) { - if rogue.GCD.IsReady(sim) { - x.run(sim, rogue) - } - } else { - panic("Unexpected builder cast failure") - } - case ShouldCast: - spell := prio.GetSpell(rogue, rogue.ComboPoints()) - if spell == nil || spell.Cast(sim, rogue.CurrentTarget) { - x.priorityItems[item.PrioIndex].CastCount += 1 - x.rotationItems = x.planRotation(sim, rogue) - if rogue.GCD.IsReady(sim) { - x.run(sim, rogue) - } - } else { - panic("Unexpected cast failure") - } - case ShouldWait: - desiredEnergy := 100.0 - if rogue.ComboPoints() == 5 { - desiredEnergy = prio.EnergyCost - } else { - if rogue.CurrentEnergy() < prio.EnergyCost && rogue.ComboPoints() >= prio.MinimumComboPoints { - desiredEnergy = prio.EnergyCost - } else if rogue.ComboPoints() < 5 { - desiredEnergy = x.builder.DefaultCast.Cost - } - } - cdAvailableTime := time.Second * 10 - if sim.CurrentTime > cdAvailableTime { - cdAvailableTime = core.NeverExpires - } - nextExpiration := cdAvailableTime - for _, otherItem := range x.rotationItems { - if otherItem.ExpiresAt < nextExpiration { - nextExpiration = otherItem.ExpiresAt - } - } - neededEnergy := desiredEnergy - rogue.CurrentEnergy() - energyAvailableTime := time.Second*time.Duration(neededEnergy/eps) + 1*time.Second - energyAt := sim.CurrentTime + energyAvailableTime - if energyAt < nextExpiration { - rogue.WaitForEnergy(sim, desiredEnergy) - } else if nextExpiration > sim.CurrentTime { - rogue.WaitUntil(sim, nextExpiration) - } else { - rogue.DoNothing() - } - } -} - -func (x *rotation_multi) energyToBuild(points int32) float64 { - costPerBuilder := x.builder.DefaultCast.Cost - - buildersNeeded := math.Ceil(float64(points) / float64(x.builderPoints)) - return buildersNeeded * costPerBuilder -} - -func (x *rotation_multi) timeToBuild(points int32, builderPoints int32, eps float64, finisherCost float64) time.Duration { - energyNeeded := x.energyToBuild(points) + finisherCost - secondsNeeded := energyNeeded / eps - globalsNeeded := math.Ceil(float64(points)/float64(builderPoints)) + 1 - // Return greater of the time it takes to use the globals and the time it takes to build the energy - return max(time.Second*time.Duration(secondsNeeded), time.Second*time.Duration(globalsNeeded)) -} - -func (x *rotation_multi) shouldCastNextRotationItem(sim *core.Simulation, rogue *Rogue, eps float64) shouldCastRotationItemResult { - if len(x.rotationItems) == 0 { - panic("Empty rotation") - } - currentEnergy := rogue.CurrentEnergy() - comboPoints := rogue.ComboPoints() - currentTime := sim.CurrentTime - item := x.rotationItems[0] - prio := x.priorityItems[item.PrioIndex] - tte := item.ExpiresAt - currentTime - clippingThreshold := time.Second * 2 - timeUntilNextGCD := rogue.GCD.TimeToReady(sim) - - // Check to see if a higher prio item will expire - if len(x.rotationItems) >= 2 { - timeElapsed := time.Second * 1 - for _, nextItem := range x.rotationItems[1:] { - if nextItem.ExpiresAt <= currentTime+timeElapsed { - return ShouldNotCast - } - timeElapsed += nextItem.MinimumBuildDuration - } - } - - // Expires before next GCD - if tte <= timeUntilNextGCD { - if comboPoints >= prio.MinimumComboPoints && currentEnergy >= (prio.EnergyCost+prio.PoolAmount) { - return ShouldCast - } else if comboPoints < prio.MinimumComboPoints && currentEnergy >= x.builder.DefaultCast.Cost { - return ShouldBuild - } else { - return ShouldWait - } - } - if comboPoints >= prio.MaximumComboPoints { // Don't need CP - // Cast - if tte <= clippingThreshold && currentEnergy >= (prio.EnergyCost+prio.PoolAmount) { - return ShouldCast - } - // Pool energy - if tte <= clippingThreshold && currentEnergy < (prio.EnergyCost+prio.PoolAmount) { - return ShouldWait - } - // We have time to squeeze in another spell - if tte > item.MinimumBuildDuration { - // Find the first lower prio item that can be cast and use it - for lpi, lowerPrio := range x.priorityItems[item.PrioIndex+1:] { - if comboPoints > lowerPrio.MinimumComboPoints && currentEnergy > lowerPrio.EnergyCost && lowerPrio.MaxCasts == 0 { - x.rotationItems = append([]rogueRotationItem{ - {ExpiresAt: currentTime, PrioIndex: lpi + item.PrioIndex + 1}, - }, x.rotationItems...) - return ShouldCast - } - } - } - // Overcap CP with builder - if x.timeToBuild(1, x.builderPoints, eps, prio.EnergyCost+prio.PoolAmount) <= tte && currentEnergy >= x.builder.DefaultCast.Cost { - return ShouldBuild - } - } else if comboPoints < prio.MinimumComboPoints { // Need CP - if currentEnergy >= x.builder.DefaultCast.Cost { - return ShouldBuild - } else { - return ShouldWait - } - } else { // Between MinimumComboPoints and MaximumComboPoints - if currentEnergy >= prio.EnergyCost+prio.PoolAmount && tte <= timeUntilNextGCD { - return ShouldCast - } - ttb := x.timeToBuild(1, 2, eps, prio.EnergyCost+prio.PoolAmount-currentEnergy) - if currentEnergy >= x.builder.DefaultCast.Cost && tte > ttb { - return ShouldBuild - } - } - return ShouldWait -} - -func (x *rotation_multi) getExpectedEnergyPerSecond(rogue *Rogue) float64 { - const finishersPerSecond = 1.0 / 6 - const averageComboPointsSpendOnFinisher = 4.0 - bonusEnergyPerSecond := float64(rogue.Talents.CombatPotency) * 3 * 0.2 * 1.0 / (rogue.AutoAttacks.OH().SwingSpeed / 1.4) - bonusEnergyPerSecond += float64(rogue.Talents.FocusedAttacks) - bonusEnergyPerSecond += float64(rogue.Talents.RelentlessStrikes) * 0.04 * 25 * finishersPerSecond * averageComboPointsSpendOnFinisher - return (core.EnergyPerTick*rogue.EnergyTickMultiplier)/core.EnergyTickDuration.Seconds() + bonusEnergyPerSecond -} - -func (x *rotation_multi) planRotation(sim *core.Simulation, rogue *Rogue) []rogueRotationItem { - var rotationItems []rogueRotationItem - eps := x.getExpectedEnergyPerSecond(rogue) - for pi, prio := range x.priorityItems { - if prio.MaxCasts > 0 && prio.CastCount >= prio.MaxCasts { - continue - } - maxCP := prio.MaximumComboPoints - for maxCP > 0 && prio.GetDuration(rogue, maxCP)+sim.CurrentTime > sim.Duration { - maxCP-- - } - var expiresAt time.Duration - if prio.Aura != nil { - expiresAt = prio.Aura.ExpiresAt() - } else if prio.MaxCasts == 1 { - expiresAt = sim.CurrentTime // TODO looks fishy, repeated expiresAt = sim.CurrentTime - } else { - expiresAt = sim.CurrentTime - } - minimumBuildDuration := x.timeToBuild(prio.MinimumComboPoints, x.builderPoints, eps, prio.EnergyCost) - maximumBuildDuration := x.timeToBuild(maxCP, x.builderPoints, eps, prio.EnergyCost) - rotationItems = append(rotationItems, rogueRotationItem{ - ExpiresAt: expiresAt, - MaximumBuildDuration: maximumBuildDuration, - MinimumBuildDuration: minimumBuildDuration, - PrioIndex: pi, - }) - } - - currentTime := sim.CurrentTime - comboPoints := rogue.ComboPoints() - currentEnergy := rogue.CurrentEnergy() - - var prioStack []rogueRotationItem - for _, item := range rotationItems { - if item.ExpiresAt >= sim.Duration { - continue - } - prio := x.priorityItems[item.PrioIndex] - maxBuildAt := item.ExpiresAt - item.MaximumBuildDuration - if prio.Aura == nil { - timeValueOfResources := time.Duration((float64(comboPoints)*x.builder.DefaultCast.Cost/float64(x.builderPoints) + currentEnergy) / eps) - maxBuildAt = currentTime - item.MaximumBuildDuration - timeValueOfResources - } - if currentTime < maxBuildAt { - // Put it on the to cast stack - prioStack = append(prioStack, item) - if prio.MinimumComboPoints > 0 { - comboPoints = 0 - } - currentTime += item.MaximumBuildDuration - } else { - cpUsed := max(0, prio.MinimumComboPoints-comboPoints) - energyUsed := max(0, prio.EnergyCost-currentEnergy) - minBuildTime := x.timeToBuild(cpUsed, x.builderPoints, eps, energyUsed) - if currentTime+minBuildTime <= item.ExpiresAt || !prio.IsFiller { - prioStack = append(prioStack, item) - currentTime = max(item.ExpiresAt, currentTime+minBuildTime) - currentEnergy = 0 - if prio.MinimumComboPoints > 0 { - comboPoints = 0 - } - } else if len(prioStack) < 1 || (prio.Aura != nil && !prio.Aura.IsActive() && !prio.IsFiller) || prio.MaxCasts == 1 { - // Plan to cast it as soon as possible - prioStack = append(prioStack, item) - currentTime += item.MinimumBuildDuration - currentEnergy = 0 - if prio.MinimumComboPoints > 0 { - comboPoints = 0 - } - } - } - } - - // Reverse - for i, j := 0, len(prioStack)-1; i < j; i, j = i+1, j-1 { - prioStack[i], prioStack[j] = prioStack[j], prioStack[i] - } - - return prioStack -} diff --git a/sim/_rogue/rotation_subtlety.go b/sim/_rogue/rotation_subtlety.go deleted file mode 100644 index 34422beb52..0000000000 --- a/sim/_rogue/rotation_subtlety.go +++ /dev/null @@ -1,340 +0,0 @@ -package rogue - -import ( - "log" - "slices" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -type rotation_subtlety struct { - prios []prio - - builder *core.Spell -} - -func (x *rotation_subtlety) setup(sim *core.Simulation, rogue *Rogue) { - x.setSubtletyBuilder(sim, rogue) - - x.prios = x.prios[:0] - - secondsPerComboPoint := func() float64 { - honorAmongThievesChance := []float64{0, 0.33, 0.66, 1.0}[rogue.Talents.HonorAmongThieves] - return 1 + 1/(float64(rogue.Options.HonorOfThievesCritRate+100)/100*honorAmongThievesChance) - } - - comboPointsPerSecond := func() float64 { - return 1 / secondsPerComboPoint() - } - - energyPerSecond := func() float64 { - return 10 * rogue.EnergyTickMultiplier - } - - if rogue.Rotation.OpenWithPremeditation && rogue.Talents.Premeditation && rogue.IsStealthed() { - x.prios = append(x.prios, prio{ - func(s *core.Simulation, r *Rogue) PriorityAction { - if rogue.Premeditation.CanCast(s, r.CurrentTarget) { - return Once - } - return Wait - }, - func(s *core.Simulation, r *Rogue) bool { - return r.Premeditation.Cast(s, r.CurrentTarget) - }, - rogue.Premeditation.DefaultCast.Cost, - }) - } - - if rogue.Rotation.OpenWithShadowstep && rogue.Talents.Shadowstep { - x.prios = append(x.prios, prio{ - func(s *core.Simulation, r *Rogue) PriorityAction { - if rogue.CurrentEnergy() > rogue.Shadowstep.DefaultCast.Cost { - return Once - } - return Wait - }, - func(s *core.Simulation, r *Rogue) bool { - return rogue.Shadowstep.Cast(sim, rogue.CurrentTarget) - }, - rogue.Shadowstep.DefaultCast.Cost, - }) - } - - // Garrote - if rogue.Rotation.OpenWithGarrote && !rogue.PseudoStats.InFrontOfTarget && rogue.IsStealthed() { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.CurrentEnergy() > rogue.Garrote.DefaultCast.Cost && rogue.IsStealthed() { - return Once - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Garrote.Cast(sim, rogue.CurrentTarget) - }, - rogue.Garrote.DefaultCast.Cost, - }) - } - - // Slice and Dice - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.SliceAndDiceAura.IsActive() { - return Skip - } - // end of combat handling - prefer Eviscerate over a mostly wasted SnD - if rogue.ComboPoints() >= 2 && rogue.sliceAndDiceDurations[rogue.ComboPoints()] >= 2*sim.GetRemainingDuration() { - return Skip - } - if rogue.ComboPoints() >= 1 && rogue.CurrentEnergy() > rogue.SliceAndDice.DefaultCast.Cost { - return Cast - } - if rogue.ComboPoints() < 1 && rogue.CurrentEnergy() > x.builder.DefaultCast.Cost && comboPointsPerSecond() >= 0.7 { - return Wait - } - if rogue.ComboPoints() < 1 && rogue.CurrentEnergy() > x.builder.DefaultCast.Cost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.SliceAndDice.Cast(sim, rogue.CurrentTarget) - }, - rogue.SliceAndDice.DefaultCast.Cost, - }) - - // Expose armor - if rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once || rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Maintain { - hasCastExpose := false - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if hasCastExpose && rogue.Rotation.ExposeArmorFrequency == proto.Rogue_Rotation_Once { - return Skip - } - timeLeft := rogue.ExposeArmorAuras.Get(rogue.CurrentTarget).RemainingDuration(sim) - minPoints := max(1, min(rogue.Rotation.MinimumComboPointsExposeArmor, 5)) - if rogue.Rotation.ExposeArmorFrequency != proto.Rogue_Rotation_Once { - minPoints = 1 - } - if timeLeft <= 0 { - if rogue.ComboPoints() < minPoints { - if rogue.CurrentEnergy() >= x.builder.DefaultCast.Cost && comboPointsPerSecond() < 1 { - return Build - } else { - return Wait - } - } else { - if rogue.CurrentEnergy() >= rogue.ExposeArmor.DefaultCast.Cost { - return Cast - } else { - return Wait - } - } - } else { - energyGained := energyPerSecond() * timeLeft.Seconds() - comboGained := comboPointsPerSecond() * timeLeft.Seconds() - cpGenerated := energyGained/x.builder.DefaultCast.Cost + comboGained - currentCP := float64(rogue.ComboPoints()) - if currentCP+cpGenerated > 5 { - return Skip - } else { - if currentCP < 5 { - if rogue.CurrentEnergy() >= x.builder.DefaultCast.Cost { - return Build - } - } - return Wait - } - } - }, - func(sim *core.Simulation, rogue *Rogue) bool { - casted := rogue.ExposeArmor.Cast(sim, rogue.CurrentTarget) - if casted { - hasCastExpose = true - } - return casted - }, - rogue.ExposeArmor.DefaultCast.Cost, - }) - } - - // Enable CDS - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - for _, mcd := range rogue.GetMajorCooldowns() { - mcd.Enable() - } - return Once - }, - func(_ *core.Simulation, _ *Rogue) bool { - return true - }, - 0, - }) - - //Shadowstep - if rogue.Talents.Shadowstep { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.Shadowstep.IsReady(sim) { - // Can we cast Rupture now? - if !rogue.Rupture.CurDot().IsActive() && rogue.ComboPoints() >= 5 && rogue.CurrentEnergy() >= rogue.Rupture.DefaultCast.Cost+rogue.Shadowstep.DefaultCast.Cost { - return Cast - } else { - return Skip - } - } - return Skip - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Shadowstep.Cast(sim, rogue.CurrentTarget) - }, - rogue.Shadowstep.DefaultCast.Cost, - }) - } - - const ruptureMinDuration = time.Second * 8 // heuristically, 3-4 Rupture ticks are better DPE than Eviscerate or Envenom - - // Rupture - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if rogue.Rupture.CurDot().IsActive() || sim.GetRemainingDuration() < ruptureMinDuration { - return Skip - } - if rogue.ComboPoints() >= 5 && rogue.CurrentEnergy() >= rogue.Rupture.DefaultCast.Cost { - return Cast - } - // don't explicitly wait here, to shorten downtime - if rogue.ComboPoints() < 5 && rogue.CurrentEnergy() >= x.builder.DefaultCast.Cost+rogue.Rupture.DefaultCast.Cost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Rupture.Cast(sim, rogue.CurrentTarget) - }, - rogue.Rupture.DefaultCast.Cost, - }) - - //Envenom - if rogue.Rotation.SubtletyFinisherPriority == proto.Rogue_Rotation_SubtletyEnvenom { - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - if !rogue.DeadlyPoison.CurDot().Aura.IsActive() { - return Skip - } - if rogue.EnvenomAura.IsActive() { - return Skip - } - if rogue.ComboPoints() >= 5 && rogue.CurrentEnergy() >= rogue.Envenom.DefaultCast.Cost { - return Cast - } - if rogue.ComboPoints() < 5 && rogue.CurrentEnergy() >= x.builder.DefaultCast.Cost+rogue.Envenom.DefaultCast.Cost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Envenom.Cast(sim, rogue.CurrentTarget) - }, - rogue.Envenom.DefaultCast.Cost, - }) - } - - // Eviscerate - x.prios = append(x.prios, prio{ - func(sim *core.Simulation, rogue *Rogue) PriorityAction { - // end of combat handling - prefer Eviscerate over Builder, heuristically - if sim.GetRemainingDuration().Seconds() < secondsPerComboPoint() && rogue.ComboPoints() >= 2 && rogue.CurrentEnergy() >= rogue.Eviscerate.DefaultCast.Cost { - return Cast - } - if rogue.ComboPoints() >= 5 && rogue.CurrentEnergy() >= rogue.Eviscerate.DefaultCast.Cost { - return Cast - } - if rogue.ComboPoints() < 5 && rogue.CurrentEnergy() >= x.builder.DefaultCast.Cost+rogue.Eviscerate.DefaultCast.Cost { - return Build - } - return Wait - }, - func(sim *core.Simulation, rogue *Rogue) bool { - return rogue.Eviscerate.Cast(sim, rogue.CurrentTarget) - }, - rogue.Eviscerate.DefaultCast.Cost, - }) -} - -func (x *rotation_subtlety) run(sim *core.Simulation, rogue *Rogue) { - for i := 0; i < len(x.prios); i++ { - switch p := x.prios[i]; p.check(sim, rogue) { - case Skip: - continue - case Build: - if rogue.ComboPoints() == 4 && rogue.CurrentEnergy() <= rogue.maxEnergy-10 { - // just wait for HaT proc - if it happens, a finisher will follow and often cost effectively 0 energy, - // so we add another GCD worth of energy headroom - rogue.DoNothing() - return - } - - x.setSubtletyBuilder(sim, rogue) - if !x.builder.Cast(sim, rogue.CurrentTarget) { - rogue.WaitForEnergy(sim, x.builder.DefaultCast.Cost) - return - } - case Cast: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - case Once: - if !p.cast(sim, rogue) { - rogue.WaitForEnergy(sim, p.cost) - return - } - x.prios = slices.Delete(x.prios, i, i+1) - i-- - case Wait: - rogue.DoNothing() - return - } - - if !rogue.GCD.IsReady(sim) { - return - } - } - log.Panic("skipped all prios") -} - -func (x *rotation_subtlety) setSubtletyBuilder(sim *core.Simulation, rogue *Rogue) { - // Garrote - if !rogue.Garrote.CurDot().Aura.IsActive() && rogue.IsStealthed() && !rogue.PseudoStats.InFrontOfTarget { - x.builder = rogue.Garrote - return - } - // Ambush - if rogue.IsStealthed() && !rogue.PseudoStats.InFrontOfTarget && rogue.HasDagger(core.MainHand) { - x.builder = rogue.Ambush - return - } - // Backstab - if rogue.Rotation.SubtletyBuilder == proto.Rogue_Rotation_BackstabSub && !rogue.PseudoStats.InFrontOfTarget && rogue.HasDagger(core.MainHand) { - x.builder = rogue.Backstab - return - } - // Ghostly Strike - if rogue.Talents.GhostlyStrike && rogue.Rotation.UseGhostlyStrike && rogue.GhostlyStrike.IsReady(sim) { - x.builder = rogue.GhostlyStrike - return - } - // Hemorrhage - if rogue.Talents.Hemorrhage { - x.builder = rogue.Hemorrhage - return - } - - // Sinister Strike - x.builder = rogue.SinisterStrike -} diff --git a/sim/_rogue/talents.go b/sim/_rogue/talents.go index 3aa72d4481..f365e7a7a2 100644 --- a/sim/_rogue/talents.go +++ b/sim/_rogue/talents.go @@ -508,11 +508,6 @@ func (rogue *Rogue) registerBladeFlurryCD() { Priority: core.CooldownPriorityDefault, ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - if rogue.Rotation.MultiTargetSliceFrequency == proto.Rogue_Rotation_Never { - // Well let's just cast BF now, no need to optimize around slices that will never be cast - return true - } - if sim.GetRemainingDuration() > cooldownDur+dur { // We'll have enough time to cast another BF, so use it immediately to make sure we get the 2nd one. return true @@ -540,16 +535,10 @@ func (rogue *Rogue) registerAdrenalineRushCD() { OnGain: func(aura *core.Aura, sim *core.Simulation) { rogue.ResetEnergyTick(sim) rogue.ApplyEnergyTickMultiplier(1.0) - if r, ok := rogue.rotation.(*rotation_multi); ok { - r.planRotation(sim, rogue) - } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { rogue.ResetEnergyTick(sim) rogue.ApplyEnergyTickMultiplier(-1.0) - if r, ok := rogue.rotation.(*rotation_multi); ok { - r.planRotation(sim, rogue) - } }, }) diff --git a/sim/_rogue/tricks_of_the_trade.go b/sim/_rogue/tricks_of_the_trade.go index e36684a4ee..bfd980a835 100644 --- a/sim/_rogue/tricks_of_the_trade.go +++ b/sim/_rogue/tricks_of_the_trade.go @@ -4,7 +4,6 @@ import ( "time" "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" ) func (rogue *Rogue) registerTricksOfTheTradeSpell() { @@ -71,20 +70,4 @@ func (rogue *Rogue) registerTricksOfTheTradeSpell() { } }, }) - - if rogue.Rotation.TricksOfTheTradeFrequency != proto.Rogue_Rotation_Never { - // TODO: Support Rogue_Rotation_Once - rogue.AddMajorCooldown(core.MajorCooldown{ - Spell: rogue.TricksOfTheTrade, - Priority: core.CooldownPriorityBloodlust, - Type: core.CooldownTypeDPS, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - if hasShadowblades { - return rogue.CurrentEnergy() <= rogue.maxEnergy-15-rogue.EnergyTickMultiplier*10 - } else { - return true - } - }, - }) - } } diff --git a/sim/_rogue/vanish.go b/sim/_rogue/vanish.go index 8cdfd3c721..7c9d370bb3 100644 --- a/sim/_rogue/vanish.go +++ b/sim/_rogue/vanish.go @@ -27,32 +27,6 @@ func (rogue *Rogue) registerVanishSpell() { rogue.AutoAttacks.CancelAutoSwing(sim) // Apply stealth rogue.StealthAura.Activate(sim) - - if !rogue.IsUsingAPL { - // Master of Subtlety - if rogue.Talents.MasterOfSubtlety > 0 { - _, premedCPs := checkPremediation(sim, rogue) - _, garroteCPs := checkGarrote(sim, rogue) - - if premedCPs > 0 && rogue.ComboPoints()+premedCPs+garroteCPs <= 5 { - rogue.Premeditation.Cast(sim, target) - } - - if garroteCPs > 0 { - rogue.Garrote.Cast(sim, target) - } - } - - // Break the Stealth effect automatically after a dely with an auto swing - pa := &core.PendingAction{ - NextActionAt: sim.CurrentTime + time.Second*time.Duration(rogue.Options.VanishBreakTime), - Priority: core.ActionPriorityAuto, - } - pa.OnAction = func(sim *core.Simulation) { - rogue.BreakStealth(sim) - rogue.AutoAttacks.EnableAutoSwing(sim) - } - } }, }) From f740a66cdcd55008be7d6c7b9475a0940a391472 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 15:56:57 -0500 Subject: [PATCH 11/26] update shaman APL only --- sim/_shaman/apl_values.go | 8 +- sim/_shaman/bloodlust.go | 13 +- sim/_shaman/elemental/elemental.go | 33 +- sim/_shaman/elemental/elemental_test.go | 12 +- sim/_shaman/elemental/rotation.go | 311 -------------- sim/_shaman/enhancement/enhancement.go | 89 +--- sim/_shaman/enhancement/enhancement_test.go | 33 +- sim/_shaman/enhancement/priority_rotation.go | 411 ------------------- sim/_shaman/enhancement/rotation.go | 31 -- sim/_shaman/fire_elemental_pet.go | 1 - sim/_shaman/fire_elemental_totem.go | 53 +-- sim/_shaman/fire_totems.go | 59 +-- sim/_shaman/restoration/restoration.go | 23 +- sim/_shaman/restoration/restoration_test.go | 8 +- sim/_shaman/restoration/rotation.go | 58 --- sim/_shaman/shaman.go | 187 +-------- sim/_shaman/shamanistic_rage.go | 36 +- sim/_shaman/talents.go | 2 +- sim/_shaman/totems.go | 59 +-- 19 files changed, 71 insertions(+), 1356 deletions(-) delete mode 100644 sim/_shaman/elemental/rotation.go delete mode 100644 sim/_shaman/enhancement/priority_rotation.go delete mode 100644 sim/_shaman/enhancement/rotation.go delete mode 100644 sim/_shaman/restoration/rotation.go diff --git a/sim/_shaman/apl_values.go b/sim/_shaman/apl_values.go index 483fa325cc..44c79cefb1 100644 --- a/sim/_shaman/apl_values.go +++ b/sim/_shaman/apl_values.go @@ -38,13 +38,13 @@ func (value *APLValueTotemRemainingTime) Type() proto.APLValueType { } func (value *APLValueTotemRemainingTime) GetDuration(sim *core.Simulation) time.Duration { if value.totemType == proto.ShamanTotems_Earth { - return max(0, value.shaman.NextTotemDrops[EarthTotem]-sim.CurrentTime) + return max(0, value.shaman.TotemExpirations[EarthTotem]-sim.CurrentTime) } else if value.totemType == proto.ShamanTotems_Air { - return max(0, value.shaman.NextTotemDrops[AirTotem]-sim.CurrentTime) + return max(0, value.shaman.TotemExpirations[AirTotem]-sim.CurrentTime) } else if value.totemType == proto.ShamanTotems_Fire { - return max(0, value.shaman.NextTotemDrops[FireTotem]-sim.CurrentTime) + return max(0, value.shaman.TotemExpirations[FireTotem]-sim.CurrentTime) } else if value.totemType == proto.ShamanTotems_Water { - return max(0, value.shaman.NextTotemDrops[WaterTotem]-sim.CurrentTime) + return max(0, value.shaman.TotemExpirations[WaterTotem]-sim.CurrentTime) } else { return 0 } diff --git a/sim/_shaman/bloodlust.go b/sim/_shaman/bloodlust.go index d542328d46..5ff548f2af 100644 --- a/sim/_shaman/bloodlust.go +++ b/sim/_shaman/bloodlust.go @@ -12,9 +12,6 @@ func (shaman *Shaman) BloodlustActionID() core.ActionID { } func (shaman *Shaman) registerBloodlustCD() { - if !shaman.SelfBuffs.Bloodlust && !shaman.IsUsingAPL { - return - } actionID := shaman.BloodlustActionID() blAuras := []*core.Aura{} @@ -24,7 +21,7 @@ func (shaman *Shaman) registerBloodlustCD() { } } - bloodlustSpell := shaman.RegisterSpell(core.SpellConfig{ + shaman.RegisterSpell(core.SpellConfig{ ActionID: actionID, Flags: core.SpellFlagAPL, @@ -72,12 +69,4 @@ func (shaman *Shaman) registerBloodlustCD() { } }, }) - - if !shaman.IsUsingAPL { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: bloodlustSpell, - Priority: core.CooldownPriorityBloodlust, - Type: core.CooldownTypeDPS, - }) - } } diff --git a/sim/_shaman/elemental/elemental.go b/sim/_shaman/elemental/elemental.go index 6da6db3843..52e44354f3 100644 --- a/sim/_shaman/elemental/elemental.go +++ b/sim/_shaman/elemental/elemental.go @@ -28,12 +28,7 @@ func NewElementalShaman(character *core.Character, options *proto.Player) *Eleme eleShamOptions := options.GetElementalShaman() selfBuffs := shaman.SelfBuffs{ - Bloodlust: eleShamOptions.Options.Bloodlust, - Shield: eleShamOptions.Options.Shield, - } - - if eleShamOptions.Rotation.Bloodlust != proto.ElementalShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = eleShamOptions.Rotation.Bloodlust == proto.ElementalShaman_Rotation_UseBloodlust + Shield: eleShamOptions.Options.Shield, } totems := &proto.ShamanTotems{} @@ -42,27 +37,10 @@ func NewElementalShaman(character *core.Character, options *proto.Player) *Eleme totems.UseFireMcd = true // Control fire totems as MCD. } - var rotation Rotation - - switch eleShamOptions.Rotation.Type { - case proto.ElementalShaman_Rotation_Adaptive: - rotation = NewAdaptiveRotation(eleShamOptions.Rotation) - case proto.ElementalShaman_Rotation_Manual: - rotation = NewManualRotation(eleShamOptions.Rotation) - default: - rotation = NewAdaptiveRotation(eleShamOptions.Rotation) - } - - inRange := eleShamOptions.Rotation.InThunderstormRange - if eleShamOptions.Options.ThunderstormRange != proto.ElementalShaman_Options_UnsetTSRange { - inRange = eleShamOptions.Options.ThunderstormRange == proto.ElementalShaman_Options_TSInRange - } + inRange := eleShamOptions.Options.ThunderstormRange == proto.ElementalShaman_Options_TSInRange ele := &ElementalShaman{ - Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, inRange), - rotation: rotation, - has4pT6: character.HasSetBonus(shaman.ItemSetSkyshatterRegalia, 4), + Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, inRange), } - ele.EnableResumeAfterManaWait(ele.tryUseGCD) if mh := ele.GetMHWeapon(); mh != nil { ele.ApplyFlametongueImbueToItem(mh, false) @@ -90,10 +68,6 @@ func NewElementalShaman(character *core.Character, options *proto.Player) *Eleme type ElementalShaman struct { *shaman.Shaman - - rotation Rotation - - has4pT6 bool } func (eleShaman *ElementalShaman) GetShaman() *shaman.Shaman { @@ -102,5 +76,4 @@ func (eleShaman *ElementalShaman) GetShaman() *shaman.Shaman { func (eleShaman *ElementalShaman) Reset(sim *core.Simulation) { eleShaman.Shaman.Reset(sim) - eleShaman.rotation.Reset(eleShaman, sim) } diff --git a/sim/_shaman/elemental/elemental_test.go b/sim/_shaman/elemental/elemental_test.go index 8e66a21ff2..c5c730d179 100644 --- a/sim/_shaman/elemental/elemental_test.go +++ b/sim/_shaman/elemental/elemental_test.go @@ -104,22 +104,18 @@ var FireElementalBasicTotems = &proto.ShamanTotems{ var PlayerOptionsAdaptive = &proto.Player_ElementalShaman{ ElementalShaman: &proto.ElementalShaman{ Options: &proto.ElementalShaman_Options{ - Shield: proto.ShamanShield_WaterShield, - Bloodlust: true, - Totems: BasicTotems, + Shield: proto.ShamanShield_WaterShield, + Totems: BasicTotems, }, - Rotation: &proto.ElementalShaman_Rotation{}, }, } var PlayerOptionsAdaptiveFireElemental = &proto.Player_ElementalShaman{ ElementalShaman: &proto.ElementalShaman{ Options: &proto.ElementalShaman_Options{ - Shield: proto.ShamanShield_WaterShield, - Bloodlust: true, - Totems: FireElementalBasicTotems, + Shield: proto.ShamanShield_WaterShield, + Totems: FireElementalBasicTotems, }, - Rotation: &proto.ElementalShaman_Rotation{}, }, } diff --git a/sim/_shaman/elemental/rotation.go b/sim/_shaman/elemental/rotation.go deleted file mode 100644 index d1da1a5da1..0000000000 --- a/sim/_shaman/elemental/rotation.go +++ /dev/null @@ -1,311 +0,0 @@ -package elemental - -import ( - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -// func (eleShaman *ElementalShaman) GetPresimOptions(_ proto.Player) *core.PresimOptions { -// return eleShaman.rotation.GetPresimOptions() -// } - -func (eleShaman *ElementalShaman) OnGCDReady(sim *core.Simulation) { - if eleShaman.IsUsingAPL { - return - } - - eleShaman.tryUseGCD(sim) -} - -func (eleShaman *ElementalShaman) tryUseGCD(sim *core.Simulation) { - if eleShaman.TryDropTotems(sim) { - return - } - - eleShaman.rotation.DoAction(eleShaman, sim) -} - -// Picks which attacks / abilities the Shaman does. -type Rotation interface { - // GetPresimOptions() *core.PresimOptions - - // Returns the action this rotation would like to take next. - DoAction(*ElementalShaman, *core.Simulation) - - // Returns this rotation to its initial state. Called before each Sim iteration. - Reset(*ElementalShaman, *core.Simulation) -} - -// ################################################################ -// -// ADAPTIVE -// -// ################################################################ -type AdaptiveRotation struct { - fnmm float64 - clmm float64 - lvbFSWait time.Duration -} - -func (rotation *AdaptiveRotation) DoAction(eleShaman *ElementalShaman, sim *core.Simulation) { - target := eleShaman.CurrentTarget - - shouldTS := false - cmp := eleShaman.CurrentManaPercent() - percent := sim.GetRemainingDurationPercent() - 0.1 - if eleShaman.Env.GetNumTargets() > 1 { - percent = 0.9 // single target we need less mana. - } - if cmp < percent { - shouldTS = true - } - if shouldTS && eleShaman.Thunderstorm.IsReady(sim) { - eleShaman.Thunderstorm.Cast(sim, target) - return - } - - fsTime := eleShaman.FlameShock.CurDot().RemainingDuration(sim) - lvTime := eleShaman.LavaBurst.CD.TimeToReady(sim) - lvCastTime := eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime) - if fsTime <= 0 && eleShaman.FlameShock.IsReady(sim) { - if !eleShaman.FlameShock.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FlameShock.CurCast.Cost) - } - return - } else if fsTime > lvCastTime { - if lvTime <= 0 { - if !eleShaman.LavaBurst.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.LavaBurst.CurCast.Cost) - } - return - } else if lvTime <= rotation.lvbFSWait && fsTime > lvCastTime+lvTime { - // If we have enough time to wait lvbFSWait and still have FS up, we should just wait to cast LvB. - eleShaman.WaitUntil(sim, sim.CurrentTime+lvTime) - return - } - } - - fsCD := eleShaman.FlameShock.CD.TimeToReady(sim) - if fsCD > fsTime { - fsTime = fsCD - } - // If FS will be needed and is ready in < lvbFSWait time, delay. - if fsTime <= rotation.lvbFSWait { - eleShaman.WaitUntil(sim, sim.CurrentTime+fsTime) - return - } - - if cmp > rotation.clmm && eleShaman.ChainLightning.IsReady(sim) { - clTime := eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime) - // Only CL if cast time > 1 second than CL or there is more than 1 target. - if clTime > core.GCDMin || eleShaman.Env.GetNumTargets() > 1 { - if !eleShaman.ChainLightning.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.ChainLightning.CurCast.Cost) - } - return - } - } else if cmp > rotation.fnmm && eleShaman.FireNova.IsReady(sim) { - if !eleShaman.FireNova.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FireNova.CurCast.Cost) - } - return - } - - if !eleShaman.LightningBolt.Cast(sim, target) { - if sim.Log != nil { - eleShaman.Log(sim, "Failed to cast LB, cost: %0.1f, current mana: %0.1f\n") - } - eleShaman.WaitForMana(sim, eleShaman.LightningBolt.CurCast.Cost) - } -} - -func (rotation *AdaptiveRotation) Reset(_ *ElementalShaman, sim *core.Simulation) { - rotation.fnmm = 1.0 - rotation.clmm = 1.0 - if len(sim.Encounter.Targets) > 4 { - // 5+ targets FN is better - rotation.fnmm = 0.33 - // Allow CL as long as you have decent mana (leaving most mana for FN) - rotation.clmm = 0.5 - } else if len(sim.Encounter.Targets) == 4 { - // 4 targets, enable both similar prio, prob looking at real AoE now (short fight) - rotation.clmm = 0.33 - rotation.fnmm = 0.33 - } else if len(sim.Encounter.Targets) == 3 { - // 3 targets, enable both, but prio CL (more efficient) - // Still trying to be very mana efficient as 3 targets - // is still often a "boss fight" and could be long. - rotation.clmm = 0.33 - rotation.fnmm = 0.66 - } else if len(sim.Encounter.Targets) == 2 { - // enable CL with 2 - rotation.clmm = 0.33 - } -} - -// func (rotation *AdaptiveRotation) GetPresimOptions() *core.PresimOptions { -// return &core.PresimOptions{ -// SetPresimPlayerOptions: func(player *proto.Player) { -// }, - -// OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool { -// return true -// }, -// } -// } - -func NewAdaptiveRotation(options *proto.ElementalShaman_Rotation) *AdaptiveRotation { - if options.LvbFsWaitMs == 0 { - options.LvbFsWaitMs = 175 - } - return &AdaptiveRotation{ - lvbFSWait: time.Duration(options.LvbFsWaitMs) * time.Millisecond, - } -} - -// ################################################################ -// -// MANUAL -// -// ################################################################ -type ManualRotation struct { - options *proto.ElementalShaman_Rotation -} - -func (rotation *ManualRotation) DoAction(eleShaman *ElementalShaman, sim *core.Simulation) { - target := eleShaman.CurrentTarget - lvbFSWait := time.Duration(rotation.options.LvbFsWaitMs) * time.Millisecond - shouldTS := false - cmp := eleShaman.CurrentManaPercent() - - // TODO: expose these percents to let user tweak - percent := sim.GetRemainingDurationPercent() - 0.1 - if eleShaman.Env.GetNumTargets() > 1 { - percent = 0.9 - } - if cmp < percent { - shouldTS = true - } - if shouldTS && rotation.options.UseThunderstorm && eleShaman.Thunderstorm.IsReady(sim) { - eleShaman.Thunderstorm.Cast(sim, target) - return - } - - fsRemain := eleShaman.FlameShock.CurDot().RemainingDuration(sim) - needFS := fsRemain <= 0 - // Only overwrite if lvb is ready right now. - if !needFS && rotation.options.OverwriteFlameshock && eleShaman.LavaBurst.CD.TimeToReady(sim) <= core.GCDDefault { - lvbTime := max(eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime), core.GCDMin) - if fsRemain < lvbTime { - needFS = true - } - } - - allowLvB := true - if rotation.options.AlwaysCritLvb { - lvbTime := max(eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime), core.GCDMin) - if fsRemain <= lvbTime { - allowLvB = false - } - } - - shouldCL := rotation.options.UseChainLightning && cmp > (rotation.options.ClMinManaPer/100) && eleShaman.ChainLightning.IsReady(sim) - clTime := eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime) - - // Never cast CL if single target and cast time <= 1 second - // This is effecively a waste of haste (that is probably temporary.) - if clTime <= time.Second && eleShaman.Env.GetNumTargets() == 1 { - shouldCL = false - } - lvbCD := eleShaman.LavaBurst.CD.TimeToReady(sim) - if shouldCL && rotation.options.UseClOnlyGap { - shouldCL = false - clCast := max(eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime), core.GCDMin) - // If LvB CD < CL cast time, we should use CL to pass the time until then. - // Or if FS is about to expire and we didn't cast LvB. - if fsRemain <= clCast || (lvbCD <= clCast) { - shouldCL = true - } - } - - fsCD := eleShaman.FlameShock.CD.TimeToReady(sim) - if fsCD > fsRemain { - fsRemain = fsCD - } - - // If FS will be needed and is ready in < lvbFSWait time, delay. - if fsRemain <= lvbFSWait && fsRemain > 0 { - eleShaman.WaitUntil(sim, sim.CurrentTime+fsRemain) - return - } - - // If LvB will be ready in < lvbFSWait time, delay - if lvbCD <= lvbFSWait && lvbCD > 0 { - eleShaman.WaitUntil(sim, sim.CurrentTime+lvbCD) - return - } - - if needFS && eleShaman.FlameShock.IsReady(sim) { - if !eleShaman.FlameShock.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FlameShock.CurCast.Cost) - } - return - } else if allowLvB && eleShaman.LavaBurst.IsReady(sim) { - if !eleShaman.LavaBurst.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.LavaBurst.CurCast.Cost) - } - return - } else if shouldCL { - if !eleShaman.ChainLightning.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.ChainLightning.CurCast.Cost) - } - return - } else if rotation.options.UseFireNova && cmp > (rotation.options.FnMinManaPer/100) && eleShaman.FireNova.IsReady(sim) { - if !eleShaman.FireNova.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FireNova.CurCast.Cost) - } - return - } - - if !eleShaman.LightningBolt.Cast(sim, target) { - if sim.Log != nil { - eleShaman.Log(sim, "Failed to cast LB, cost: %0.1f, current mana: %0.1f\n") - } - eleShaman.WaitForMana(sim, eleShaman.LightningBolt.CurCast.Cost) - } -} - -func (rotation *ManualRotation) Reset(_ *ElementalShaman, _ *core.Simulation) { -} - -// func (rotation *ManualRotation) GetPresimOptions() *core.PresimOptions { -// return &core.PresimOptions{ -// SetPresimPlayerOptions: func(player *proto.Player) { -// }, - -// OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool { -// return true -// }, -// } -// } - -func NewManualRotation(options *proto.ElementalShaman_Rotation) *ManualRotation { - return &ManualRotation{ - options: options, - } -} - -// A single action that an Agent can take. -type AgentAction interface { - GetActionID() core.ActionID - - // TODO: Maybe change this to 'ResourceCost' - // Amount of mana required to perform the action. - GetManaCost() float64 - - // Do the action. Returns whether the action was successful. An unsuccessful - // action indicates that the prerequisites, like resource cost, were not met. - Cast(sim *core.Simulation) bool -} diff --git a/sim/_shaman/enhancement/enhancement.go b/sim/_shaman/enhancement/enhancement.go index 5cc0dc3b8e..9f3422d3a7 100644 --- a/sim/_shaman/enhancement/enhancement.go +++ b/sim/_shaman/enhancement/enhancement.go @@ -3,7 +3,6 @@ package enhancement import ( "time" - "github.com/wowsims/sod/sim/common" "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/shaman" @@ -30,15 +29,9 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh enhOptions := options.GetEnhancementShaman() selfBuffs := shaman.SelfBuffs{ - Bloodlust: enhOptions.Options.Bloodlust, - Shield: enhOptions.Options.Shield, - ImbueMH: enhOptions.Options.ImbueMh, - ImbueOH: enhOptions.Options.ImbueOh, - } - - // Override with new rotation option bloodlust. - if enhOptions.Rotation.Bloodlust != proto.EnhancementShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = enhOptions.Rotation.Bloodlust == proto.EnhancementShaman_Rotation_UseBloodlust + Shield: enhOptions.Options.Shield, + ImbueMH: enhOptions.Options.ImbueMh, + ImbueOH: enhOptions.Options.ImbueOh, } totems := &proto.ShamanTotems{} @@ -50,9 +43,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, true), } - enh.EnableResumeAfterManaWait(enh.OnGCDReady) - enh.rotation = NewPriorityRotation(enh, enhOptions.Rotation) - // Enable Auto Attacks for this spec enh.EnableAutoAttacks(enh, core.AutoAttackOptions{ MainHand: enh.WeaponFromMainHand(enh.DefaultMeleeCritMultiplier()), @@ -66,12 +56,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh enh.EnableItemSwap(enhOptions.Rotation.ItemSwap, enh.DefaultMeleeCritMultiplier(), enh.DefaultMeleeCritMultiplier(), 0) } - if enhOptions.Rotation.LightningboltWeave { - enh.maelstromWeaponMinStack = enhOptions.Rotation.MaelstromweaponMinStack - } else { - enh.maelstromWeaponMinStack = 5 - } - if !enh.HasMHWeapon() { enh.SelfBuffs.ImbueMH = proto.ShamanImbue_NoImbue } @@ -89,8 +73,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh SpiritWolf2: enh.NewSpiritWolf(2), } - enh.ShamanisticRageManaThreshold = enhOptions.Rotation.ShamanisticRageManaThreshold - return enh } @@ -107,14 +89,6 @@ func (enh *EnhancementShaman) getImbueProcMask(imbue proto.ShamanImbue) core.Pro type EnhancementShaman struct { *shaman.Shaman - - rotation Rotation - maelstromWeaponMinStack int32 - - // for weaving Lava Burst or Lightning Bolt - previousSwingAt time.Duration - - scheduler common.GCDScheduler } func (enh *EnhancementShaman) GetShaman() *shaman.Shaman { @@ -135,11 +109,9 @@ func (enh *EnhancementShaman) Initialize() { enh.ApplySyncType(proto.ShamanSyncType_Auto) }) } - enh.DelayDPSCooldowns(3 * time.Second) } func (enh *EnhancementShaman) Reset(sim *core.Simulation) { - enh.previousSwingAt = 0 enh.Shaman.Reset(sim) enh.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, false) } @@ -181,58 +153,3 @@ func (enh *EnhancementShaman) ApplySyncType(syncType proto.ShamanSyncType) { enh.AutoAttacks.SetReplaceMHSwing(nil) } } - -func (enh *EnhancementShaman) CastLightningBoltWeave(sim *core.Simulation, reactionTime time.Duration) bool { - previousAttack := sim.CurrentTime - enh.previousSwingAt - reactionTime = core.TernaryDuration(previousAttack < reactionTime, reactionTime-previousAttack, 0) - - //calculate cast times for weaving - lbCastTime := enh.ApplyCastSpeed(enh.LightningBolt.DefaultCast.CastTime-(time.Millisecond*time.Duration(500*enh.MaelstromWeaponAura.GetStacks()))) + reactionTime - //calculate swing times for weaving - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - - if lbCastTime < timeUntilSwing { - if reactionTime > 0 { - reactionTime += sim.CurrentTime - - enh.HardcastWaitUntil(sim, reactionTime, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.LightningBolt.Cast(sim, enh.CurrentTarget) - }) - - enh.WaitUntil(sim, reactionTime) - return true - } - return enh.LightningBolt.Cast(sim, enh.CurrentTarget) - } - - return false -} - -func (enh *EnhancementShaman) CastLavaBurstWeave(sim *core.Simulation, reactionTime time.Duration) bool { - previousAttack := sim.CurrentTime - enh.previousSwingAt - reactionTime = core.TernaryDuration(previousAttack < reactionTime, reactionTime-previousAttack, 0) - - //calculate cast times for weaving - lvbCastTime := enh.ApplyCastSpeed(enh.LavaBurst.DefaultCast.CastTime) + reactionTime - //calculate swing times for weaving - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - - if lvbCastTime < timeUntilSwing { - if reactionTime > 0 { - reactionTime += sim.CurrentTime - - enh.HardcastWaitUntil(sim, reactionTime, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.LavaBurst.Cast(sim, enh.CurrentTarget) - }) - - enh.WaitUntil(sim, reactionTime) - return true - } - - return enh.LavaBurst.Cast(sim, enh.CurrentTarget) - } - - return false -} diff --git a/sim/_shaman/enhancement/enhancement_test.go b/sim/_shaman/enhancement/enhancement_test.go index 1aaa48832c..4f9ec0246f 100644 --- a/sim/_shaman/enhancement/enhancement_test.go +++ b/sim/_shaman/enhancement/enhancement_test.go @@ -80,32 +80,28 @@ var StandardTalents = "053030152-30405003105021333031131031051" var PlayerOptionsWFWF = &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ - Options: enhShamWFWF, - Rotation: &proto.EnhancementShaman_Rotation{}, + Options: enhShamWFWF, }, } var PlayerOptionsFTFT = &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ - Options: enhShamFTFT, - Rotation: &proto.EnhancementShaman_Rotation{}, + Options: enhShamFTFT, }, } var enhShamWFWF = &proto.EnhancementShaman_Options{ - Shield: proto.ShamanShield_WaterShield, - Bloodlust: true, - SyncType: proto.ShamanSyncType_DelayOffhandSwings, - ImbueMh: proto.ShamanImbue_WindfuryWeapon, - ImbueOh: proto.ShamanImbue_WindfuryWeapon, + Shield: proto.ShamanShield_WaterShield, + SyncType: proto.ShamanSyncType_DelayOffhandSwings, + ImbueMh: proto.ShamanImbue_WindfuryWeapon, + ImbueOh: proto.ShamanImbue_WindfuryWeapon, } var enhShamFTFT = &proto.EnhancementShaman_Options{ - Shield: proto.ShamanShield_LightningShield, - Bloodlust: true, - SyncType: proto.ShamanSyncType_Auto, - ImbueMh: proto.ShamanImbue_FlametongueWeaponDownrank, - ImbueOh: proto.ShamanImbue_FlametongueWeapon, + Shield: proto.ShamanShield_LightningShield, + SyncType: proto.ShamanSyncType_Auto, + ImbueMh: proto.ShamanImbue_FlametongueWeaponDownrank, + ImbueOh: proto.ShamanImbue_FlametongueWeapon, Totems: &proto.ShamanTotems{ Earth: proto.EarthTotem_StrengthOfEarthTotem, Air: proto.AirTotem_WindfuryTotem, @@ -116,11 +112,10 @@ var enhShamFTFT = &proto.EnhancementShaman_Options{ } var enhShamWFFT = &proto.EnhancementShaman_Options{ - Shield: proto.ShamanShield_LightningShield, - Bloodlust: true, - SyncType: proto.ShamanSyncType_NoSync, - ImbueMh: proto.ShamanImbue_WindfuryWeapon, - ImbueOh: proto.ShamanImbue_FlametongueWeapon, + Shield: proto.ShamanShield_LightningShield, + SyncType: proto.ShamanSyncType_NoSync, + ImbueMh: proto.ShamanImbue_WindfuryWeapon, + ImbueOh: proto.ShamanImbue_FlametongueWeapon, } var FullConsumes = &proto.Consumes{ diff --git a/sim/_shaman/enhancement/priority_rotation.go b/sim/_shaman/enhancement/priority_rotation.go deleted file mode 100644 index 79bbaf506b..0000000000 --- a/sim/_shaman/enhancement/priority_rotation.go +++ /dev/null @@ -1,411 +0,0 @@ -package enhancement - -import ( - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -// Default Priority Order -const ( - LightningBolt = iota - StormstrikeApplyDebuff - WeaveLavaBurst - WeaveLightningBolt - MagmaTotem - Stormstrike - FlameShock - EarthShock - FrostShock - FireNova - DelayedWeave - LightningShield - DropAllTotems - LavaLash - NumberSpells // Used to get the max number of spells in the prio list, keep at bottoom -) - -type PriorityRotation struct { - options *proto.EnhancementShaman_Rotation - spellPriority []Spell -} - -type Cast func(sim *core.Simulation, target *core.Unit) bool -type Condition func(sim *core.Simulation, target *core.Unit) bool -type ReadyAt func() time.Duration - -// Holds all the spell info we need to make decisions -type Spell struct { - readyAt ReadyAt - cast Cast - // Must pass this check to cast or use readyAt, a special condition to be met - condition Condition -} - -func NewPriorityRotation(enh *EnhancementShaman, options *proto.EnhancementShaman_Rotation) *PriorityRotation { - rotation := &PriorityRotation{ - options: options, - } - - rotation.buildPriorityRotation(enh) - - return rotation -} - -func (rotation *PriorityRotation) buildPriorityRotation(enh *EnhancementShaman) { - stormstrikeApplyDebuff := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return !enh.StormstrikeDebuffAura(target).IsActive() - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.Stormstrike.IsReady(sim) && enh.Stormstrike.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.Stormstrike.ReadyAt() - }, - } - - instantLightningBolt := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MaelstromWeaponAura.GetStacks() == 5 - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.LightningBolt.Cast(sim, enh.CurrentTarget) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - chainLightning := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MaelstromWeaponAura.GetStacks() == 5 && enh.ChainLightning.IsReady(sim) - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.ChainLightning.Cast(sim, enh.CurrentTarget) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - stormstrike := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //Checking if we learned the spell, ie untalented - return enh.Stormstrike != nil - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - //TODO add in SS delay so we don't lose flametongues, if Last attack = current time - return enh.Stormstrike.IsReady(sim) && enh.Stormstrike.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.Stormstrike.ReadyAt() - }, - } - - weaveLightningBolt := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.LightningboltWeave && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack && enh.CurrentMana() >= enh.LightningBolt.DefaultCast.Cost - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - reactionTime := time.Millisecond * time.Duration(rotation.options.AutoWeaveDelay) - return enh.CastLightningBoltWeave(sim, reactionTime) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - weaveLavaBurst := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.CurrentMana() < enh.LavaBurst.CurCast.Cost { - return false - } - - return rotation.options.LavaburstWeave && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - reactionTime := time.Millisecond * time.Duration(rotation.options.AutoWeaveDelay) - return rotation.options.LavaburstWeave && enh.LavaBurst.IsReady(sim) && enh.CastLavaBurstWeave(sim, reactionTime) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - flameShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - fsDot := enh.FlameShock.Dot(target) - return rotation.options.WeaveFlameShock && fsDot.RemainingDuration(sim) <= time.Duration(rotation.options.FlameShockClipTicks)*fsDot.TickLength - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FlameShock.IsReady(sim) && enh.FlameShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.FlameShock.ReadyAt() - }, - } - - earthShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_Earth - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.EarthShock.IsReady(sim) && enh.EarthShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.EarthShock.ReadyAt() - }, - } - - frostShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_Frost - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FrostShock.IsReady(sim) && enh.FrostShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.EarthShock.ReadyAt() - }, - } - - lightningShield := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.LightningShield != nil && !enh.LightningShieldAura.IsActive() - }, - cast: func(sim *core.Simulation, _ *core.Unit) bool { - return enh.LightningShield.Cast(sim, nil) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - fireNova := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.Totems.Fire == proto.FireTotem_NoFireTotem { - return false - } - - return enh.NextTotemDrops[2] > sim.CurrentTime && enh.CurrentMana() > rotation.options.FirenovaManaThreshold - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FireNova.IsReady(sim) && enh.FireNova.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.FireNova.ReadyAt() - }, - } - - lavaLash := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //Checking if we learned the spell, ie untalented - return enh.LavaLash != nil && enh.AutoAttacks.IsDualWielding - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - //TODO add in LL delay so we don't lose flametongues, if Last attack = current time - return enh.LavaLash.IsReady(sim) && enh.LavaLash.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.LavaLash.ReadyAt() - }, - } - - magmaTotem := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //TODO : rework totems to make them easier to work with. - return enh.Totems.Fire == proto.FireTotem_MagmaTotem && enh.NextTotemDrops[2] <= sim.CurrentTime - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MagmaTotem.Cast(sim, target) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - dropAllTotems := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return true - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - // TODO : totems need to be dropped all at once. - return enh.TryDropTotems(sim) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - delayedWeave := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.CurrentMana() < enh.LightningBolt.DefaultCast.Cost { - return false - } - - return rotation.options.LightningboltWeave && rotation.options.DelayGcdWeave > 0 && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - if timeUntilSwing <= time.Millisecond*time.Duration(rotation.options.DelayGcdWeave) && timeUntilSwing != 0 { - delay := enh.AutoAttacks.NextAttackAt() + time.Millisecond*100 - if delay < sim.CurrentTime { - return false - } - - enh.HardcastWaitUntil(sim, delay, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.CastLightningBoltWeave(sim, 0) - }) - - enh.WaitUntil(sim, delay) - return true - } - - return false - }, - readyAt: func() time.Duration { - return 0 - }, - } - - //Normal Priority Rotation - var spellPriority []Spell - if rotation.options.RotationType == proto.EnhancementShaman_Rotation_Priority { - spellPriority = make([]Spell, NumberSpells) - spellPriority[StormstrikeApplyDebuff] = stormstrikeApplyDebuff - spellPriority[LightningBolt] = instantLightningBolt - spellPriority[Stormstrike] = stormstrike - spellPriority[FlameShock] = flameShock - spellPriority[EarthShock] = earthShock - spellPriority[LightningShield] = lightningShield - spellPriority[FireNova] = fireNova - spellPriority[LavaLash] = lavaLash - spellPriority[WeaveLightningBolt] = weaveLightningBolt - spellPriority[FrostShock] = frostShock - spellPriority[WeaveLavaBurst] = weaveLavaBurst - spellPriority[MagmaTotem] = magmaTotem - spellPriority[DropAllTotems] = dropAllTotems - spellPriority[DelayedWeave] = delayedWeave - } - - //Custom Priority Rotation - if rotation.options.RotationType == proto.EnhancementShaman_Rotation_Custom && rotation.options.CustomRotation != nil { - spellPriority := make([]Spell, 0, len(rotation.options.CustomRotation.Spells)) - - // Turn weaving off, will enable them if they have been added. - rotation.options.LightningboltWeave = false - rotation.options.LavaburstWeave = false - rotation.options.WeaveFlameShock = false - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_None - for _, customSpellProto := range rotation.options.CustomRotation.Spells { - switch customSpellProto.Spell { - case int32(proto.EnhancementShaman_Rotation_StormstrikeDebuffMissing): - spellPriority = append(spellPriority, stormstrikeApplyDebuff) - case int32(proto.EnhancementShaman_Rotation_LightningBolt): - spellPriority = append(spellPriority, instantLightningBolt) - case int32(proto.EnhancementShaman_Rotation_ChainLightning): - spellPriority = append(spellPriority, chainLightning) - case int32(proto.EnhancementShaman_Rotation_LightningBoltWeave): - rotation.options.LightningboltWeave = true - spellPriority = append(spellPriority, weaveLightningBolt) - case int32(proto.EnhancementShaman_Rotation_LightningBoltDelayedWeave): - rotation.options.LightningboltWeave = true - spellPriority = append(spellPriority, delayedWeave) - case int32(proto.EnhancementShaman_Rotation_Stormstrike): - spellPriority = append(spellPriority, stormstrike) - case int32(proto.EnhancementShaman_Rotation_FlameShock): - rotation.options.WeaveFlameShock = true - spellPriority = append(spellPriority, flameShock) - case int32(proto.EnhancementShaman_Rotation_FireNova): - spellPriority = append(spellPriority, fireNova) - case int32(proto.EnhancementShaman_Rotation_LavaLash): - spellPriority = append(spellPriority, lavaLash) - case int32(proto.EnhancementShaman_Rotation_EarthShock): - if rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_None { - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_Earth - spellPriority = append(spellPriority, earthShock) - } - case int32(proto.EnhancementShaman_Rotation_LightningShield): - spellPriority = append(spellPriority, lightningShield) - case int32(proto.EnhancementShaman_Rotation_FrostShock): - if rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_None { - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_Frost - spellPriority = append(spellPriority, frostShock) - } - case int32(proto.EnhancementShaman_Rotation_LavaBurst): - rotation.options.LavaburstWeave = true - spellPriority = append(spellPriority, weaveLavaBurst) - case int32(proto.EnhancementShaman_Rotation_MagmaTotem): - spellPriority = append(spellPriority, magmaTotem) - } - } - - rotation.spellPriority = spellPriority - return - } - - rotation.spellPriority = spellPriority -} - -func (rotation *PriorityRotation) DoAction(enh *EnhancementShaman, sim *core.Simulation) { - target := enh.CurrentTarget - - cheapestSpell := enh.LightningBolt.CurCast.Cost - if enh.LavaLash != nil { - cheapestSpell = enh.LavaLash.CurCast.Cost - } - - // Incase the array is empty - if len(rotation.spellPriority) == 0 { - enh.DoNothing() - } - - //Mana guard, our cheapest spell. - if enh.CurrentMana() < cheapestSpell { - // Lets top off lightning shield stacks before waiting for mana. - if enh.LightningShield != nil && enh.LightningShieldAura.GetStacks() < 3 { - enh.LightningShield.Cast(sim, nil) - } - enh.WaitForMana(sim, cheapestSpell) - return - } - - // We could choose to not wait for auto attacks if we don't have any MW stacks, - // this would reduce the amount of DoAction calls by a little bit; might not be a issue though. - upcomingCD := enh.AutoAttacks.NextAttackAt() - var cast Cast - for _, spell := range rotation.spellPriority { - if !spell.condition(sim, target) { - continue - } - - if spell.cast(sim, target) { - return - } - - readyAt := spell.readyAt() - if readyAt > sim.CurrentTime && (upcomingCD > readyAt || upcomingCD < sim.CurrentTime) { - upcomingCD = readyAt - cast = spell.cast - } - } - - //Lets wait on a upcoming CD or AutoAttack - enh.WaitUntil(sim, upcomingCD) - - //Incase the next auto is our best CD then there are no spells to cast. - if cast != nil { - //We have a upcoming CD and we know what to cast lets just do that. - enh.HardcastWaitUntil(sim, upcomingCD, func(sim *core.Simulation, target *core.Unit) { - enh.GCD.Reset() - cast(sim, target) - }) - } -} - -func (rotation *PriorityRotation) Reset(_ *EnhancementShaman, _ *core.Simulation) { - -} diff --git a/sim/_shaman/enhancement/rotation.go b/sim/_shaman/enhancement/rotation.go deleted file mode 100644 index 6ce9b1f636..0000000000 --- a/sim/_shaman/enhancement/rotation.go +++ /dev/null @@ -1,31 +0,0 @@ -package enhancement - -import ( - "github.com/wowsims/sod/sim/core" -) - -func (enh *EnhancementShaman) OnAutoAttack(sim *core.Simulation, _ *core.Spell) { - enh.previousSwingAt = sim.CurrentTime -} - -func (enh *EnhancementShaman) OnGCDReady(sim *core.Simulation) { - // TODO move this into the rotation, also this uses waitForMana if it was unable to cast the totem - // that will need to be pulled out so we are not waiting for a magma totem mana cost - enh.rotation.DoAction(enh, sim) -} - -type Rotation interface { - DoAction(*EnhancementShaman, *core.Simulation) - Reset(*EnhancementShaman, *core.Simulation) -} - -// CUSTOM ROTATION (advanced) (also WIP). -// -// TODO: figure out how to do this (probably too complicated to copy hunters) -type AgentAction interface { - GetActionID() core.ActionID - - GetManaCost() float64 - - Cast(sim *core.Simulation) bool -} diff --git a/sim/_shaman/fire_elemental_pet.go b/sim/_shaman/fire_elemental_pet.go index 3d200bfe20..282e89d3ec 100644 --- a/sim/_shaman/fire_elemental_pet.go +++ b/sim/_shaman/fire_elemental_pet.go @@ -90,7 +90,6 @@ func (fireElemental *FireElemental) OnGCDReady(sim *core.Simulation) { fireNovaCasts := fireElemental.FireNova.SpellMetrics[0].Casts if fireBlastCasts == maxFireBlastCasts && fireNovaCasts == maxFireNovaCasts { - fireElemental.DoNothing() return } diff --git a/sim/_shaman/fire_elemental_totem.go b/sim/_shaman/fire_elemental_totem.go index 3f4d7806bd..b9afd73072 100644 --- a/sim/_shaman/fire_elemental_totem.go +++ b/sim/_shaman/fire_elemental_totem.go @@ -3,14 +3,14 @@ package shaman import ( "time" - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/wotlk/sim/core" + "github.com/wowsims/wotlk/sim/core/proto" ) const fireTotemDuration time.Duration = time.Second * 120 func (shaman *Shaman) registerFireElementalTotem() { - if !shaman.Totems.UseFireElemental && !shaman.IsUsingAPL { + if !shaman.Totems.UseFireElemental { return } @@ -34,17 +34,15 @@ func (shaman *Shaman) registerFireElementalTotem() { }, CD: core.Cooldown{ Timer: shaman.NewTimer(), - Duration: time.Minute * time.Duration(10), + Duration: time.Minute * time.Duration(core.TernaryFloat64(shaman.HasMajorGlyph(proto.ShamanMajorGlyph_GlyphOfFireElementalTotem), 5, 10)), }, }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, _ *core.Spell) { // TODO: ToW needs a unique buff/debuff aura for each raidmember/target. // Otherwise we will be possibly disabling another ele shaman's ToW debuff/buff. - if shaman.Totems.Fire == proto.FireTotem_TotemOfWrath { - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + fireTotemDuration - } else if shaman.Totems.Fire != proto.FireTotem_NoFireTotem && !shaman.Totems.UseFireMcd { - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + fireTotemDuration + if shaman.Totems.Fire != proto.FireTotem_NoFireTotem { + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + fireTotemDuration } shaman.MagmaTotem.AOEDot().Cancel(sim) @@ -55,50 +53,13 @@ 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 { - shaman.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, true) - } - // Add a dummy aura to show in metrics fireElementalAura.Activate(sim) }, }) - //Enh has 1.5seconds GCD also, so just going to wait the normal 1.5 instead of using the dynamic Spell GCD - var castWindow = 1550 * time.Millisecond - - enhTier10Aura := shaman.GetAura("Maelstrom Power") - shaman.AddMajorCooldown(core.MajorCooldown{ Spell: shaman.FireElementalTotem, - Type: core.CooldownTypeUnknown, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - success := false - if enhTier10Aura != nil && shaman.Totems.EnhTierTenBonus && shaman.fireElementalSnapShot != nil { - if enhTier10Aura.IsActive() { - success = shaman.fireElementalSnapShot.CanSnapShot(sim, castWindow) - } else if sim.CurrentTime+fireTotemDuration > sim.Encounter.Duration { - success = true - } - } else if sim.CurrentTime > 1*time.Second && shaman.fireElementalSnapShot == nil { - success = true - } else if sim.Encounter.Duration <= 120*time.Second && sim.CurrentTime >= 10*time.Second { - success = true - } else if sim.Encounter.Duration > 120*time.Second && sim.CurrentTime >= 20*time.Second { - success = true - } else if shaman.fireElementalSnapShot != nil { - success = shaman.fireElementalSnapShot.CanSnapShot(sim, castWindow) - } - - if success && shaman.fireElementalSnapShot != nil { - shaman.castFireElemental = true - shaman.fireElementalSnapShot.ActivateMajorCooldowns(sim) - shaman.fireElementalSnapShot.ResetProcTrackers() - shaman.castFireElemental = false - } - - return success - }, + Type: core.CooldownTypeDPS, }) } diff --git a/sim/_shaman/fire_totems.go b/sim/_shaman/fire_totems.go index f228ef7114..15a4f936f8 100644 --- a/sim/_shaman/fire_totems.go +++ b/sim/_shaman/fire_totems.go @@ -4,23 +4,9 @@ import ( "time" "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" ) func (shaman *Shaman) registerSearingTotemSpell() { - var extraCastCondition core.CanCastCondition - if !shaman.IsUsingAPL && shaman.Totems.Fire == proto.FireTotem_SearingTotem && shaman.Totems.UseFireMcd { - extraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - if shaman.Totems.Fire != proto.FireTotem_SearingTotem { - return false - } - if shaman.SearingTotem.Dot(shaman.CurrentTarget).IsActive() || shaman.FireElemental.IsEnabled() || shaman.FireElementalTotem.IsReady(sim) { - return false - } - return true - } - } - shaman.SearingTotem = shaman.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 58704}, SpellSchool: core.SpellSchoolFire, @@ -38,7 +24,6 @@ func (shaman *Shaman) registerSearingTotemSpell() { GCD: time.Second, }, }, - ExtraCastCondition: extraCastCondition, BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -66,37 +51,13 @@ func (shaman *Shaman) registerSearingTotemSpell() { shaman.MagmaTotem.AOEDot().Cancel(sim) shaman.FireElemental.Disable(sim) spell.Dot(sim.GetTargetUnit(0)).Apply(sim) - if !shaman.Totems.UseFireMcd { - // +1 needed because of rounding issues with totem tick time. - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*60 + 1 - } + // +1 needed because of rounding issues with totem tick time. + shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*60 + 1 }, }) - - if extraCastCondition == nil { - return - } - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: shaman.SearingTotem, - Priority: core.CooldownPriorityDefault, // TODO needs to be altered due to snap shotting. - Type: core.CooldownTypeDPS, - }) } func (shaman *Shaman) registerMagmaTotemSpell() { - var extraCastCondition core.CanCastCondition - if !shaman.IsUsingAPL && shaman.Totems.Fire == proto.FireTotem_MagmaTotem && shaman.Totems.UseFireMcd { - extraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - if shaman.Totems.Fire != proto.FireTotem_MagmaTotem { - return false - } - if shaman.MagmaTotem.AOEDot().IsActive() || shaman.FireElemental.IsEnabled() || shaman.FireElementalTotem.IsReady(sim) { - return false - } - return true - } - } - shaman.MagmaTotem = shaman.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 58734}, SpellSchool: core.SpellSchoolFire, @@ -114,7 +75,6 @@ func (shaman *Shaman) registerMagmaTotemSpell() { GCD: time.Second, }, }, - ExtraCastCondition: extraCastCondition, BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -141,19 +101,8 @@ func (shaman *Shaman) registerMagmaTotemSpell() { shaman.SearingTotem.Dot(shaman.CurrentTarget).Cancel(sim) shaman.FireElemental.Disable(sim) spell.AOEDot().Apply(sim) - if !shaman.Totems.UseFireMcd { - // +1 needed because of rounding issues with totem tick time. - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*20 + 1 - } + // +1 needed because of rounding issues with totem tick time. + shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*20 + 1 }, }) - - if extraCastCondition == nil { - return - } - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: shaman.MagmaTotem, - Priority: core.CooldownPriorityDefault, // TODO needs to be altered due to snap shotting. - Type: core.CooldownTypeDPS, - }) } diff --git a/sim/_shaman/restoration/restoration.go b/sim/_shaman/restoration/restoration.go index 7042234489..7a8b0d361f 100644 --- a/sim/_shaman/restoration/restoration.go +++ b/sim/_shaman/restoration/restoration.go @@ -1,8 +1,6 @@ package restoration import ( - "time" - "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/shaman" @@ -29,12 +27,7 @@ func NewRestorationShaman(character *core.Character, options *proto.Player) *Res restoShamOptions := options.GetRestorationShaman() selfBuffs := shaman.SelfBuffs{ - Bloodlust: restoShamOptions.Options.Bloodlust, - Shield: restoShamOptions.Options.Shield, - } - - if restoShamOptions.Rotation.Bloodlust != proto.RestorationShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = restoShamOptions.Rotation.Bloodlust == proto.RestorationShaman_Rotation_UseBloodlust + Shield: restoShamOptions.Options.Shield, } totems := &proto.ShamanTotems{} @@ -43,16 +36,9 @@ func NewRestorationShaman(character *core.Character, options *proto.Player) *Res } resto := &RestorationShaman{ - Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, false), - rotation: restoShamOptions.Rotation, + Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, false), } - // can only use earth shield if specc'd - resto.rotation.UseEarthShield = resto.rotation.UseEarthShield && resto.Talents.EarthShield - resto.earthShieldPPM = restoShamOptions.Options.EarthShieldPPM - - resto.EnableResumeAfterManaWait(resto.tryUseGCD) - if resto.HasMHWeapon() { resto.ApplyEarthlivingImbueToItem(resto.GetMHWeapon()) } @@ -65,10 +51,6 @@ func NewRestorationShaman(character *core.Character, options *proto.Player) *Res type RestorationShaman struct { *shaman.Shaman - - rotation *proto.RestorationShaman_Rotation - earthShieldPPM int32 - lastEarthShieldProc time.Duration } func (resto *RestorationShaman) GetShaman() *shaman.Shaman { @@ -77,7 +59,6 @@ func (resto *RestorationShaman) GetShaman() *shaman.Shaman { func (resto *RestorationShaman) Reset(sim *core.Simulation) { resto.Shaman.Reset(sim) - resto.lastEarthShieldProc = -core.NeverExpires } func (resto *RestorationShaman) GetMainTarget() *core.Unit { // TODO: make this just grab first player that isn't self. diff --git a/sim/_shaman/restoration/restoration_test.go b/sim/_shaman/restoration/restoration_test.go index 3b76ec2f6e..9ac7efd605 100644 --- a/sim/_shaman/restoration/restoration_test.go +++ b/sim/_shaman/restoration/restoration_test.go @@ -78,14 +78,12 @@ var BasicTotems = &proto.ShamanTotems{ } var restoShamOptions = &proto.RestorationShaman_Options{ - Shield: proto.ShamanShield_WaterShield, - Bloodlust: true, - Totems: BasicTotems, + Shield: proto.ShamanShield_WaterShield, + Totems: BasicTotems, } var PlayerOptionsStandard = &proto.Player_RestorationShaman{ RestorationShaman: &proto.RestorationShaman{ - Options: restoShamOptions, - Rotation: &proto.RestorationShaman_Rotation{}, + Options: restoShamOptions, }, } diff --git a/sim/_shaman/restoration/rotation.go b/sim/_shaman/restoration/rotation.go deleted file mode 100644 index 12c93a1c8a..0000000000 --- a/sim/_shaman/restoration/rotation.go +++ /dev/null @@ -1,58 +0,0 @@ -package restoration - -import ( - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (resto *RestorationShaman) OnGCDReady(sim *core.Simulation) { - resto.tryUseGCD(sim) -} - -func (resto *RestorationShaman) tryUseGCD(sim *core.Simulation) { - - // TODO: This could actually just be made as a PA that runs and triggers the shield instead of part of the rotation here. - es := resto.EarthShield.Hot(resto.CurrentTarget) - if es.IsActive() && resto.earthShieldPPM > 0 { - procTime := time.Duration(60.0 / float64(resto.earthShieldPPM) * float64(time.Second)) - lastProc := resto.lastEarthShieldProc - if lastProc+procTime < sim.CurrentTime { - es.OnSpellHitTaken(es.Aura, sim, nil, &core.SpellResult{Outcome: core.OutcomeHit, Target: resto.CurrentTarget}) - resto.lastEarthShieldProc = sim.CurrentTime - } - } - - if resto.TryDropTotems(sim) { - return - } - - var spell *core.Spell - switch resto.rotation.PrimaryHeal { - case proto.ShamanHealSpell_AutoHeal: - if len(resto.Party.Players) > 3 { - spell = resto.ChainHeal - } else { - // TODO: lots of things to consider here... - spell = resto.LesserHealingWave - } - case proto.ShamanHealSpell_LesserHealingWave: - spell = resto.LesserHealingWave - case proto.ShamanHealSpell_HealingWave: - panic("healing wave not implemented yet") - spell = resto.HealingWave - case proto.ShamanHealSpell_ChainHeal: - spell = resto.ChainHeal - } - - if resto.rotation.UseEarthShield && !es.IsActive() { - spell = resto.EarthShield - } else if resto.rotation.UseRiptide && !resto.Riptide.Hot(resto.CurrentTarget).IsActive() && resto.Riptide.IsReady(sim) { - spell = resto.Riptide - } - - if !spell.Cast(sim, resto.CurrentTarget) { - resto.WaitForMana(sim, spell.CurCast.Cost) - } -} diff --git a/sim/_shaman/shaman.go b/sim/_shaman/shaman.go index 7e1600b445..899f7d9ba4 100644 --- a/sim/_shaman/shaman.go +++ b/sim/_shaman/shaman.go @@ -61,10 +61,9 @@ func NewShaman(character *core.Character, talents string, totems *proto.ShamanTo // Which buffs this shaman is using. type SelfBuffs struct { - Bloodlust bool - Shield proto.ShamanShield - ImbueMH proto.ShamanImbue - ImbueOH proto.ShamanImbue + Shield proto.ShamanShield + ImbueMH proto.ShamanImbue + ImbueOH proto.ShamanImbue } // Indexes into NextTotemDrops for self buffs @@ -81,17 +80,13 @@ type Shaman struct { thunderstormInRange bool // flag if thunderstorm will be in range. - ShamanisticRageManaThreshold float64 //% of mana to use sham. rage at - Talents *proto.ShamanTalents SelfBuffs SelfBuffs Totems *proto.ShamanTotems - // The type of totem which should be dropped next and time to drop it, for - // each totem type (earth, air, fire, water). - NextTotemDropType [4]int32 - NextTotemDrops [4]time.Duration + // The expiration time of each totem (earth, air, fire, water). + TotemExpirations [4]time.Duration LightningBolt *core.Spell LightningBoltLO *core.Spell @@ -117,10 +112,8 @@ type Shaman struct { FeralSpirit *core.Spell SpiritWolves *SpiritWolves - castFireElemental bool - FireElemental *FireElemental - FireElementalTotem *core.Spell - fireElementalSnapShot *core.SnapshotManager + FireElemental *FireElemental + FireElementalTotem *core.Spell MagmaTotem *core.Spell ManaSpringTotem *core.Spell @@ -221,8 +214,6 @@ func (shaman *Shaman) AddPartyBuffs(partyBuffs *proto.PartyBuffs) { } func (shaman *Shaman) Initialize() { - enableSnapshot := shaman.Totems.BonusSpellpower == 0 - shaman.registerChainLightningSpell() shaman.registerFeralSpirit() shaman.registerFireElementalTotem() @@ -250,23 +241,6 @@ func (shaman *Shaman) Initialize() { shaman.registerCallOfTheElements() shaman.registerBloodlustCD() - - if shaman.Totems.UseFireElemental && enableSnapshot { - shaman.fireElementalSnapShot = core.NewSnapshotManager(shaman.GetCharacter()) - shaman.setupProcTrackers() - } - - if shaman.Talents.SpiritWeapons { - shaman.PseudoStats.ThreatMultiplier -= 0.3 - } - - // Healing stream totem applies a HoT (aura) and so needs to be handled as a pre-pull action - // instead of during init/reset. - if shaman.Totems.Water == proto.WaterTotem_HealingStreamTotem { - shaman.RegisterPrepullAction(0, func(sim *core.Simulation) { - shaman.HealingStreamTotem.Cast(sim, &shaman.Unit) - }) - } } func (shaman *Shaman) RegisterHealingSpells() { @@ -299,151 +273,8 @@ func (shaman *Shaman) RegisterHealingSpells() { } func (shaman *Shaman) Reset(sim *core.Simulation) { - if shaman.Totems.UseFireElemental { - shaman.setupFireElementalCooldowns() - shaman.castFireElemental = false - } - - // Check to see if we are casting a totem to set its expire time. - for i := range shaman.NextTotemDrops { - shaman.NextTotemDrops[i] = core.NeverExpires - switch i { - case AirTotem: - if shaman.Totems.Air != proto.AirTotem_NoAirTotem { - shaman.NextTotemDrops[i] = TotemRefreshTime5M - shaman.NextTotemDropType[i] = int32(shaman.Totems.Air) - } - case EarthTotem: - if shaman.Totems.Earth != proto.EarthTotem_NoEarthTotem { - shaman.NextTotemDrops[i] = TotemRefreshTime5M - shaman.NextTotemDropType[i] = int32(shaman.Totems.Earth) - } - case FireTotem: - shaman.NextTotemDropType[FireTotem] = int32(shaman.Totems.Fire) - if shaman.NextTotemDropType[FireTotem] != int32(proto.FireTotem_NoFireTotem) { - if shaman.NextTotemDropType[FireTotem] != int32(proto.FireTotem_TotemOfWrath) && - shaman.NextTotemDropType[FireTotem] != int32(proto.FireTotem_FlametongueTotem) { - if !shaman.Totems.UseFireMcd { - shaman.NextTotemDrops[FireTotem] = 0 - } - } else { - shaman.NextTotemDrops[FireTotem] = TotemRefreshTime5M - if shaman.NextTotemDropType[FireTotem] == int32(proto.FireTotem_TotemOfWrath) { - shaman.applyToWDebuff(sim) - } - } - } - case WaterTotem: - shaman.NextTotemDropType[i] = int32(shaman.Totems.Water) - shaman.NextTotemDrops[i] = TotemRefreshTime5M - } - } - - shaman.FlameShock.CD.Reset() -} - -func (shaman *Shaman) setupProcTrackers() { - snapshotManager := shaman.fireElementalSnapShot - - snapshotManager.AddProc(40212, "Potion of Wild Magic", true) - snapshotManager.AddProc(33697, "Blood Fury", true) - snapshotManager.AddProc(59620, "Berserking MH Proc", false) - snapshotManager.AddProc(59620, "Berserking OH Proc", false) - - //AP Ring Procs - snapshotManager.AddProc(44308, "Signet of Edward the Odd Proc", false) - snapshotManager.AddProc(50401, "Ashen Band of Unmatched Vengeance Proc", false) - snapshotManager.AddProc(50402, "Ashen Band of Endless Vengeance Proc", false) - snapshotManager.AddProc(52571, "Ashen Band of Unmatched Might Proc", false) - snapshotManager.AddProc(52572, "Ashen Band of Endless Might Proc", false) - - //SP Trinket Procs - snapshotManager.AddProc(40255, "Dying Curse Proc", false) - snapshotManager.AddProc(40682, "Sundial of the Exiled Proc", false) - snapshotManager.AddProc(37660, "Forge Ember Proc", false) - snapshotManager.AddProc(45518, "Flare of the Heavens Proc", false) - snapshotManager.AddProc(54572, "Charred Twilight Scale Proc", false) - snapshotManager.AddProc(54588, "Charred Twilight Scale H Proc", false) - snapshotManager.AddProc(47213, "Abyssal Rune Proc", false) - snapshotManager.AddProc(45490, "Pandora's Plea Proc", false) - snapshotManager.AddProc(50348, "Dislodged Foreign Object H", false) - snapshotManager.AddProc(50353, "Dislodged Foreign Object", false) - snapshotManager.AddProc(50360, "Phylactery of the Nameless Lich Proc", false) - snapshotManager.AddProc(50365, "Phylactery of the Nameless Lich H Proc", false) - snapshotManager.AddProc(50345, "Muradin's Spyglass H Proc", false) - snapshotManager.AddProc(50340, "Muradin's Spyglass Proc", false) - - // SP Ring Procs - snapshotManager.AddProc(50398, "Ashen Band of Endless Destruction", false) - - //AP Trinket Procs - snapshotManager.AddProc(40684, "Mirror of Truth Proc", false) - snapshotManager.AddProc(45522, "Blood of the Old God Proc", false) - snapshotManager.AddProc(40767, "Sonic Booster Proc", false) - snapshotManager.AddProc(44914, "Anvil of Titans Proc", false) - snapshotManager.AddProc(45286, "Pyrite Infuser Proc", false) - snapshotManager.AddProc(47214, "Banner of Victory Proc", false) - snapshotManager.AddProc(49074, "Coren's Chromium Coaster Proc", false) - snapshotManager.AddProc(50342, "Whispering Fanged Skull Proc", false) - snapshotManager.AddProc(50343, "Whispering Fanged Skull H Proc", false) - snapshotManager.AddProc(54569, "Sharpened Twilight Scale Proc", false) - snapshotManager.AddProc(54590, "Sharpened Twilight Scale H Proc", false) - snapshotManager.AddProc(47115, "Deaths Verdict Agility Proc", false) - snapshotManager.AddProc(47131, "Deaths Verdict H Agility Proc", false) - snapshotManager.AddProc(47303, "Deaths Choice Agility Proc", false) - snapshotManager.AddProc(47464, "Deaths Choice H Agility Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will Strength Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H Strength Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will Agility Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H Agility Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will AP Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H AP Proc", false) - - // Tier Bonus - snapshotManager.AddProc(70831, "Maelstrom Power", false) -} - -func (shaman *Shaman) setupFireElementalCooldowns() { - if shaman.fireElementalSnapShot == nil { - return - } - - shaman.fireElementalSnapShot.ClearMajorCooldowns() - - // blood fury (orc) - shaman.fireElementalCooldownSync(core.ActionID{SpellID: 33697}, false) - - // potion of Wild Magic - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 40212}, true) - - //active sp trinkets - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37873}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 45148}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 48724}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 50357}, false) - - // active ap trinkets - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 35937}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 36871}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37166}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37556}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37557}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38080}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38081}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38761}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 39257}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 45263}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 46086}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 47734}, false) -} - -func (shaman *Shaman) fireElementalCooldownSync(actionID core.ActionID, isPotion bool) { - if majorCd := shaman.Character.GetMajorCooldown(actionID); majorCd != nil { - majorCd.ShouldActivate = func(sim *core.Simulation, character *core.Character) bool { - return shaman.castFireElemental || (shaman.FireElementalTotem.CD.TimeToReady(sim) > majorCd.Spell.CD.Duration && !isPotion) || shaman.FireElementalTotem.CD.ReadyAt() > shaman.Env.Encounter.Duration - } - - shaman.fireElementalSnapShot.AddMajorCooldown(majorCd) + if shaman.Totems.Fire == proto.FireTotem_TotemOfWrath { + shaman.applyToWDebuff(sim) } } diff --git a/sim/_shaman/shamanistic_rage.go b/sim/_shaman/shamanistic_rage.go index 3404b7ac1b..55072bc211 100644 --- a/sim/_shaman/shamanistic_rage.go +++ b/sim/_shaman/shamanistic_rage.go @@ -12,11 +12,6 @@ func (shaman *Shaman) registerShamanisticRageCD() { return } - t10Bonus := false - if shaman.HasSetBonus(ItemSetFrostWitchBattlegear, 2) { - t10Bonus = true - } - actionID := core.ActionID{SpellID: 30823} ppmm := shaman.AutoAttacks.NewPPMManager(15, core.ProcMaskMelee) manaMetrics := shaman.NewManaMetrics(actionID) @@ -26,15 +21,9 @@ func (shaman *Shaman) registerShamanisticRageCD() { Duration: time.Second * 15, OnGain: func(aura *core.Aura, sim *core.Simulation) { aura.Unit.PseudoStats.DamageTakenMultiplier *= 0.7 - if t10Bonus { - aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.12 - } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { aura.Unit.PseudoStats.DamageTakenMultiplier /= 0.7 - if t10Bonus { - aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.12 - } }, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if !result.Landed() { @@ -66,22 +55,11 @@ func (shaman *Shaman) registerShamanisticRageCD() { }, }) - if shaman.IsUsingAPL { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeMana, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - return character.CurrentManaPercent() <= 0.2 - }, - }) - } else { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeMana, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - manaReserve := shaman.ShamanisticRageManaThreshold / 100 * shaman.MaxMana() - return character.CurrentMana() <= manaReserve - }, - }) - } + shaman.AddMajorCooldown(core.MajorCooldown{ + Spell: spell, + Type: core.CooldownTypeMana, + ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { + return character.CurrentManaPercent() <= 0.2 + }, + }) } diff --git a/sim/_shaman/talents.go b/sim/_shaman/talents.go index bcd9740a9b..0ed58234e1 100644 --- a/sim/_shaman/talents.go +++ b/sim/_shaman/talents.go @@ -474,7 +474,7 @@ func (shaman *Shaman) registerManaTideTotemCD() { // TODO: Current water totem buff needs to be removed from party/raid. if shaman.Totems.Water != proto.WaterTotem_NoWaterTotem { - shaman.NextTotemDrops[WaterTotem] = sim.CurrentTime + time.Second*12 + shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + time.Second*12 } }, }) diff --git a/sim/_shaman/totems.go b/sim/_shaman/totems.go index b4fc699ee7..c83ec6257b 100644 --- a/sim/_shaman/totems.go +++ b/sim/_shaman/totems.go @@ -24,7 +24,7 @@ func (shaman *Shaman) newTotemSpellConfig(baseCost float64, spellID int32) core. func (shaman *Shaman) registerWrathOfAirTotemSpell() { config := shaman.newTotemSpellConfig(0.11, 3738) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[AirTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[AirTotem] = sim.CurrentTime + time.Second*300 } shaman.WrathOfAirTotem = shaman.RegisterSpell(config) } @@ -32,7 +32,7 @@ func (shaman *Shaman) registerWrathOfAirTotemSpell() { func (shaman *Shaman) registerWindfuryTotemSpell() { config := shaman.newTotemSpellConfig(0.11, 8512) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[AirTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[AirTotem] = sim.CurrentTime + time.Second*300 } shaman.WindfuryTotem = shaman.RegisterSpell(config) } @@ -40,7 +40,7 @@ func (shaman *Shaman) registerWindfuryTotemSpell() { func (shaman *Shaman) registerManaSpringTotemSpell() { config := shaman.newTotemSpellConfig(0.04, 58774) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[WaterTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + time.Second*300 } shaman.ManaSpringTotem = shaman.RegisterSpell(config) } @@ -72,7 +72,7 @@ func (shaman *Shaman) registerHealingStreamTotemSpell() { }, } config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - shaman.NextTotemDrops[WaterTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + time.Second*300 for _, agent := range shaman.Party.Players { spell.Hot(&agent.GetCharacter().Unit).Activate(sim) } @@ -83,7 +83,7 @@ func (shaman *Shaman) registerHealingStreamTotemSpell() { func (shaman *Shaman) registerTotemOfWrathSpell() { config := shaman.newTotemSpellConfig(0.05, 57722) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + time.Second*300 shaman.applyToWDebuff(sim) } shaman.TotemOfWrath = shaman.RegisterSpell(config) @@ -99,7 +99,7 @@ func (shaman *Shaman) applyToWDebuff(sim *core.Simulation) { func (shaman *Shaman) registerFlametongueTotemSpell() { config := shaman.newTotemSpellConfig(0.11, 58656) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + time.Second*300 } shaman.FlametongueTotem = shaman.RegisterSpell(config) } @@ -107,7 +107,7 @@ func (shaman *Shaman) registerFlametongueTotemSpell() { func (shaman *Shaman) registerStrengthOfEarthTotemSpell() { config := shaman.newTotemSpellConfig(0.1, 58643) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[EarthTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 } shaman.StrengthOfEarthTotem = shaman.RegisterSpell(config) } @@ -115,7 +115,7 @@ func (shaman *Shaman) registerStrengthOfEarthTotemSpell() { func (shaman *Shaman) registerTremorTotemSpell() { config := shaman.newTotemSpellConfig(0.02, 8143) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[EarthTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 } shaman.TremorTotem = shaman.RegisterSpell(config) } @@ -123,7 +123,7 @@ func (shaman *Shaman) registerTremorTotemSpell() { func (shaman *Shaman) registerStoneskinTotemSpell() { config := shaman.newTotemSpellConfig(0.1, 58753) config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - shaman.NextTotemDrops[EarthTotem] = sim.CurrentTime + time.Second*300 + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 } shaman.StoneskinTotem = shaman.RegisterSpell(config) } @@ -187,47 +187,6 @@ func (shaman *Shaman) registerCallOfTheElements() { }) } -func (shaman *Shaman) NextTotemAt(_ *core.Simulation) time.Duration { - return min(shaman.NextTotemDrops[0], shaman.NextTotemDrops[1], shaman.NextTotemDrops[2], shaman.NextTotemDrops[3]) -} - -// TryDropTotems will check to see if totems need to be re-cast. -// -// Returns whether we tried to cast a totem, regardless of whether it succeeded. -func (shaman *Shaman) TryDropTotems(sim *core.Simulation) bool { - var spell *core.Spell - - casted := false - for totemTypeIdx, totemExpiration := range shaman.NextTotemDrops { - spell = nil - nextDrop := shaman.NextTotemDropType[totemTypeIdx] - if sim.CurrentTime >= totemExpiration { - switch totemTypeIdx { - case AirTotem: - spell = shaman.getAirTotemSpell(proto.AirTotem(nextDrop)) - case EarthTotem: - spell = shaman.getEarthTotemSpell(proto.EarthTotem(nextDrop)) - case FireTotem: - spell = shaman.getFireTotemSpell(proto.FireTotem(nextDrop)) - case WaterTotem: - spell = shaman.getWaterTotemSpell(proto.WaterTotem(nextDrop)) - } - } - if spell != nil { - if success := spell.Cast(sim, shaman.CurrentTarget); !success { - shaman.WaitForMana(sim, spell.CurCast.Cost) - return true - } - casted = true - } - } - - if casted { - shaman.WaitUntil(sim, sim.CurrentTime+time.Second) - } - return casted -} - func (shaman *Shaman) getAirTotemSpell(totemType proto.AirTotem) *core.Spell { switch totemType { case proto.AirTotem_WrathOfAirTotem: From 2927dc9012dcb5296c659fedb49098d3f61a3028 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 15:57:04 -0500 Subject: [PATCH 12/26] update raid_bench_test.go --- sim/raid_bench_test.go | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/sim/raid_bench_test.go b/sim/raid_bench_test.go index 30c82a07a5..7fb1d9b70e 100644 --- a/sim/raid_bench_test.go +++ b/sim/raid_bench_test.go @@ -18,9 +18,6 @@ var castersWithElemental = &proto.Party{ Equipment: MoonkinEquipment, Spec: &proto.Player_BalanceDruid{ BalanceDruid: &proto.BalanceDruid{ - Rotation: &proto.BalanceDruid_Rotation{ - Type: proto.BalanceDruid_Rotation_Default, - }, Options: &proto.BalanceDruid_Options{ InnervateTarget: &proto.UnitReference{}, }, @@ -39,9 +36,6 @@ var castersWithElemental = &proto.Party{ Equipment: ShadowEquipment, Spec: &proto.Player_ShadowPriest{ ShadowPriest: &proto.ShadowPriest{ - Rotation: &proto.ShadowPriest_Rotation{ - RotationType: proto.ShadowPriest_Rotation_Ideal, - }, Options: &proto.ShadowPriest_Options{ UseShadowfiend: true, }, @@ -60,12 +54,8 @@ var castersWithElemental = &proto.Party{ Equipment: ElementalEquipment, Spec: &proto.Player_ElementalShaman{ ElementalShaman: &proto.ElementalShaman{ - Rotation: &proto.ElementalShaman_Rotation{ - Type: proto.ElementalShaman_Rotation_Adaptive, - }, Options: &proto.ElementalShaman_Options{ - Shield: proto.ShamanShield_WaterShield, - Bloodlust: true, + Shield: proto.ShamanShield_WaterShield, Totems: &proto.ShamanTotems{ Earth: proto.EarthTotem_TremorTotem, Air: proto.AirTotem_WrathOfAirTotem, @@ -91,7 +81,6 @@ var castersWithElemental = &proto.Party{ Options: &proto.Mage_Options{ Armor: proto.Mage_Options_MageArmor, }, - Rotation: &proto.Mage_Rotation{}, }, }, Consumes: &proto.Consumes{}, @@ -114,9 +103,6 @@ var castersWithResto = &proto.Party{ Equipment: MoonkinEquipment, Spec: &proto.Player_BalanceDruid{ BalanceDruid: &proto.BalanceDruid{ - Rotation: &proto.BalanceDruid_Rotation{ - Type: proto.BalanceDruid_Rotation_Default, - }, Options: &proto.BalanceDruid_Options{ InnervateTarget: &proto.UnitReference{ Type: proto.UnitReference_Player, @@ -138,9 +124,6 @@ var castersWithResto = &proto.Party{ Equipment: ShadowEquipment, Spec: &proto.Player_ShadowPriest{ ShadowPriest: &proto.ShadowPriest{ - Rotation: &proto.ShadowPriest_Rotation{ - RotationType: proto.ShadowPriest_Rotation_Ideal, - }, Options: &proto.ShadowPriest_Options{ UseShadowfiend: true, }, @@ -162,7 +145,6 @@ var castersWithResto = &proto.Party{ Options: &proto.Mage_Options{ Armor: proto.Mage_Options_MageArmor, }, - Rotation: &proto.Mage_Rotation{}, }, }, Consumes: &proto.Consumes{}, @@ -192,7 +174,6 @@ func BenchmarkSimulate(b *testing.B) { Equipment: EnhancementEquipment, Spec: &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ - Rotation: &proto.EnhancementShaman_Rotation{}, Options: &proto.EnhancementShaman_Options{ Shield: proto.ShamanShield_LightningShield, Bloodlust: true, From 9f7ca045b7643f4802dace07a91f74269517ac82 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 16:04:13 -0500 Subject: [PATCH 13/26] update warlock APL only --- sim/warlock/dps/dps_warlock_test.go | 44 ++++++++++----------------- sim/warlock/drain_life.go | 1 - sim/warlock/pet.go | 1 - sim/warlock/rain_of_fire.go | 3 +- sim/warlock/tank/tank_warlock_test.go | 44 ++++++++++----------------- sim/warlock/warlock.go | 15 --------- 6 files changed, 33 insertions(+), 75 deletions(-) diff --git a/sim/warlock/dps/dps_warlock_test.go b/sim/warlock/dps/dps_warlock_test.go index 9f977e1b7f..9df18a7ed5 100644 --- a/sim/warlock/dps/dps_warlock_test.go +++ b/sim/warlock/dps/dps_warlock_test.go @@ -80,62 +80,50 @@ var AfflictionTalents = "2350002030023510253500331151--550000051" var DemonologyTalents = "-203203301035012530135201351-550000052" var DestructionTalents = "-03310030003-05203205210331051335230351" -var defaultDestroRotation = &proto.Warlock_Rotation{} - -var defaultDestroOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultDestroOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } var DefaultDestroWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultDestroOptions, - Rotation: defaultDestroRotation, + Options: defaultDestroOptions, }, } // --------------------------------------- var DefaultAfflictionWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultAfflictionOptions, - Rotation: defaultAfflictionRotation, + Options: defaultAfflictionOptions, }, } var afflictionItemSwap = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultAfflictionOptions, - Rotation: afflictionItemSwapRotation, + Options: defaultAfflictionOptions, }, } -var defaultAfflictionOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultAfflictionOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } -var defaultAfflictionRotation = &proto.Warlock_Rotation{} - -var afflictionItemSwapRotation = &proto.Warlock_Rotation{} - // --------------------------------------- var DefaultDemonologyWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultDemonologyOptions, - Rotation: defaultDemonologyRotation, + Options: defaultDemonologyOptions, }, } -var defaultDemonologyOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultDemonologyOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } -var defaultDemonologyRotation = &proto.Warlock_Rotation{} - // --------------------------------------------------------- var FullConsumes = &proto.Consumes{ diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index ebcdeadaf6..4bc7dcdb94 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -135,7 +135,6 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { spellConfig.Flags |= core.SpellFlagPureDot } else { spellConfig.Flags |= core.SpellFlagChanneled - spellConfig.Cast.DefaultCast.ChannelTime = time.Second * 5 } return spellConfig diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index 2e768171ea..9059b1cbca 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -100,7 +100,6 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { } wp.EnableManaBarWithModifier(cfg.PowerModifier) - wp.EnableResumeAfterManaWait(wp.OnGCDReady) wp.AddStatDependency(stats.Strength, stats.AttackPower, 2) wp.AddStat(stats.AttackPower, -20) diff --git a/sim/warlock/rain_of_fire.go b/sim/warlock/rain_of_fire.go index 4e8a09b463..b74a622092 100644 --- a/sim/warlock/rain_of_fire.go +++ b/sim/warlock/rain_of_fire.go @@ -31,8 +31,7 @@ func (warlock *Warlock) getRainOfFireBaseConfig(rank int) core.SpellConfig { }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: core.GCDDefault, - ChannelTime: time.Second * 8, + GCD: core.GCDDefault, }, }, Dot: core.DotConfig{ diff --git a/sim/warlock/tank/tank_warlock_test.go b/sim/warlock/tank/tank_warlock_test.go index 4a0b184f0c..79da171732 100644 --- a/sim/warlock/tank/tank_warlock_test.go +++ b/sim/warlock/tank/tank_warlock_test.go @@ -80,62 +80,50 @@ var AfflictionTalents = "2350002030023510253500331151--550000051" var DemonologyTalents = "-203203301035012530135201351-550000052" var DestructionTalents = "-03310030003-05203205210331051335230351" -var defaultDestroRotation = &proto.Warlock_Rotation{} - -var defaultDestroOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultDestroOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } var DefaultDestroWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultDestroOptions, - Rotation: defaultDestroRotation, + Options: defaultDestroOptions, }, } // --------------------------------------- var DefaultAfflictionWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultAfflictionOptions, - Rotation: defaultAfflictionRotation, + Options: defaultAfflictionOptions, }, } var afflictionItemSwap = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultAfflictionOptions, - Rotation: afflictionItemSwapRotation, + Options: defaultAfflictionOptions, }, } -var defaultAfflictionOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultAfflictionOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } -var defaultAfflictionRotation = &proto.Warlock_Rotation{} - -var afflictionItemSwapRotation = &proto.Warlock_Rotation{} - // --------------------------------------- var DefaultDemonologyWarlock = &proto.Player_Warlock{ Warlock: &proto.Warlock{ - Options: defaultDemonologyOptions, - Rotation: defaultDemonologyRotation, + Options: defaultDemonologyOptions, }, } -var defaultDemonologyOptions = &proto.Warlock_Options{ - Armor: proto.Warlock_Options_DemonArmor, - Summon: proto.Warlock_Options_Imp, - WeaponImbue: proto.Warlock_Options_NoWeaponImbue, +var defaultDemonologyOptions = &proto.WarlockOptions{ + Armor: proto.WarlockOptions_DemonArmor, + Summon: proto.WarlockOptions_Imp, + WeaponImbue: proto.WarlockOptions_NoWeaponImbue, } -var defaultDemonologyRotation = &proto.Warlock_Rotation{} - // --------------------------------------------------------- var FullConsumes = &proto.Consumes{ diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index f411a75cf9..0620a3e543 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -65,20 +65,6 @@ type Warlock struct { PreviousTime time.Duration petStmBonusSP float64 - acl []ActionCondition -} - -type ACLaction int - -const ( - ACLCast ACLaction = iota - ACLNext - ACLRecast -) - -type ActionCondition struct { - Spell *core.Spell - Condition func(*core.Simulation) (ACLaction, *core.Unit) } func (warlock *Warlock) GetCharacter() *core.Character { @@ -164,7 +150,6 @@ func (warlock *Warlock) HasRune(rune proto.WarlockRune) bool { } func (warlock *Warlock) OnGCDReady(sim *core.Simulation) { - warlock.DoNothing() } // Agent is a generic way to access underlying warlock on any of the agents. From 4f2b6289e7e1b42cc892d61133b3a984d3b3ae77 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 16:14:01 -0500 Subject: [PATCH 14/26] update enhancement.go --- sim/_shaman/enhancement/enhancement.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sim/_shaman/enhancement/enhancement.go b/sim/_shaman/enhancement/enhancement.go index 9f3422d3a7..31de5fe78c 100644 --- a/sim/_shaman/enhancement/enhancement.go +++ b/sim/_shaman/enhancement/enhancement.go @@ -52,10 +52,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 !enh.HasMHWeapon() { enh.SelfBuffs.ImbueMH = proto.ShamanImbue_NoImbue } @@ -113,7 +109,6 @@ func (enh *EnhancementShaman) Initialize() { func (enh *EnhancementShaman) Reset(sim *core.Simulation) { enh.Shaman.Reset(sim) - enh.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}, false) } func (enh *EnhancementShaman) AutoSyncWeapons() proto.ShamanSyncType { From 36a6e8175ccd95c7c39ce49f731ab82ca1a93145 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 16:39:29 -0500 Subject: [PATCH 15/26] update warrior APL only --- proto/warrior.proto | 2 - sim/warrior/bloodthirst.go | 6 +- sim/warrior/dps/dps_warrior.go | 58 ++------- sim/warrior/dps/dps_warrior_test.go | 15 ++- sim/warrior/heroic_strike_cleave.go | 22 +--- sim/warrior/protection/protection_warrior.go | 30 +---- .../protection/protection_warrior_test.go | 7 +- sim/warrior/protection/rotation.go | 113 ------------------ sim/warrior/shouts.go | 11 +- sim/warrior/warrior.go | 21 ++-- 10 files changed, 42 insertions(+), 243 deletions(-) delete mode 100644 sim/warrior/protection/rotation.go diff --git a/proto/warrior.proto b/proto/warrior.proto index c331b0fa2b..e528d7ce99 100644 --- a/proto/warrior.proto +++ b/proto/warrior.proto @@ -92,7 +92,6 @@ message Warrior { message Options { double starting_rage = 1; bool use_recklessness = 2; - bool use_shattering_throw = 4; WarriorShout shout = 3; bool stance_snapshot = 6; bool disable_expertise_gemming = 7; @@ -108,7 +107,6 @@ message ProtectionWarrior { double starting_rage = 1; WarriorShout shout = 4; - bool use_shattering_throw = 8; } Options options = 3; } diff --git a/sim/warrior/bloodthirst.go b/sim/warrior/bloodthirst.go index f2ac6122a0..931f06745e 100644 --- a/sim/warrior/bloodthirst.go +++ b/sim/warrior/bloodthirst.go @@ -45,11 +45,7 @@ func (warrior *Warrior) registerBloodthirstSpell(cdTimer *core.Timer) { core.StartDelayedAction(sim, core.DelayedActionOptions{ DoAt: sim.CurrentTime + warrior.Bloodthirst.CD.Duration, OnAction: func(_ *core.Simulation) { - if warrior.IsUsingAPL { - warrior.Rotation.DoNextAction(sim) - } else if warrior.Bloodthirst.CanCast(sim, target) { - warrior.Bloodthirst.Cast(sim, target) - } + warrior.Rotation.DoNextAction(sim) }, }) }, diff --git a/sim/warrior/dps/dps_warrior.go b/sim/warrior/dps/dps_warrior.go index 77d00d5516..8d62e6be1c 100644 --- a/sim/warrior/dps/dps_warrior.go +++ b/sim/warrior/dps/dps_warrior.go @@ -1,9 +1,6 @@ package dps import ( - "time" - - "github.com/wowsims/sod/sim/common" "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/warrior" @@ -29,19 +26,7 @@ func RegisterDpsWarrior() { type DpsWarrior struct { *warrior.Warrior - Options *proto.Warrior_Options - Rotation *proto.Warrior_Rotation - CustomRotation *common.CustomRotation - - // Prevent swapping stances until this time, to account for human reaction time. - canSwapStanceAt time.Duration - // Last time sunder was applied. Used for maintaining sunder even if sunder is enabled as debuff in individual sim - lastSunderAt time.Duration - - maintainSunder bool - thunderClapNext bool - - castSlamAt time.Duration + Options *proto.Warrior_Options } func NewDpsWarrior(character *core.Character, options *proto.Player) *DpsWarrior { @@ -49,10 +34,9 @@ func NewDpsWarrior(character *core.Character, options *proto.Player) *DpsWarrior war := &DpsWarrior{ Warrior: warrior.NewWarrior(character, options.TalentsString, warrior.WarriorInputs{ - ShoutType: warOptions.Options.Shout, + StanceSnapshot: warOptions.Options.StanceSnapshot, }), - Rotation: warOptions.Rotation, - Options: warOptions.Options, + Options: warOptions.Options, } rbo := core.RageBarOptions{ @@ -66,7 +50,7 @@ func NewDpsWarrior(character *core.Character, options *proto.Player) *DpsWarrior rbo.OHSwingSpeed = oh.SwingSpeed } - war.EnableRageBar(rbo, func(sim *core.Simulation) {}) + war.EnableRageBar(rbo) war.EnableAutoAttacks(war, core.AutoAttackOptions{ MainHand: war.WeaponFromMainHand(war.DefaultMeleeCritMultiplier()), OffHand: war.WeaponFromOffHand(war.DefaultMeleeCritMultiplier()), @@ -88,51 +72,25 @@ func (war *DpsWarrior) GetWarrior() *warrior.Warrior { func (war *DpsWarrior) Initialize() { war.Warrior.Initialize() - war.RegisterHSOrCleave(war.Rotation.UseCleave) - war.RegisterRendSpell() - if war.Options.UseRecklessness { war.RegisterRecklessnessCD() } - // This makes the behavior of these options more intuitive in the individual sim. - if war.Env.Raid.Size() == 1 { - if war.Rotation.SunderArmor == proto.Warrior_Rotation_SunderArmorHelpStack { - war.SunderArmorAuras.Get(war.CurrentTarget).Duration = core.NeverExpires - } else if war.Rotation.SunderArmor == proto.Warrior_Rotation_SunderArmorMaintain { - war.SunderArmorAuras.Get(war.CurrentTarget).Duration = time.Second * 30 - } - } - - if war.Rotation.StanceOption == proto.Warrior_Rotation_DefaultStance { - if war.Warrior.PrimaryTalentTree == warrior.FuryTree { - war.Rotation.StanceOption = proto.Warrior_Rotation_BerserkerStance - } else { - war.Rotation.StanceOption = proto.Warrior_Rotation_BattleStance - } - } - - if war.Rotation.StanceOption == proto.Warrior_Rotation_BerserkerStance { + if war.PrimaryTalentTree == warrior.FuryTree { war.BerserkerStanceAura.BuildPhase = core.CharacterBuildPhaseTalents - } else if war.Rotation.StanceOption == proto.Warrior_Rotation_BattleStance { + } else if war.PrimaryTalentTree == warrior.ArmsTree { war.BattleStanceAura.BuildPhase = core.CharacterBuildPhaseTalents } - - war.DelayDPSCooldownsForArmorDebuffs(time.Second * 10) } func (war *DpsWarrior) Reset(sim *core.Simulation) { - if war.Rotation.StanceOption == proto.Warrior_Rotation_BerserkerStance { + if war.PrimaryTalentTree == warrior.FuryTree { war.Warrior.Reset(sim) war.BerserkerStanceAura.Activate(sim) war.Stance = warrior.BerserkerStance - } else if war.Rotation.StanceOption == proto.Warrior_Rotation_BattleStance { + } else if war.PrimaryTalentTree == warrior.ArmsTree { war.Warrior.Reset(sim) war.BattleStanceAura.Activate(sim) war.Stance = warrior.BattleStance } - war.canSwapStanceAt = 0 - war.maintainSunder = war.Rotation.SunderArmor != proto.Warrior_Rotation_SunderArmorNone - war.castSlamAt = 0 - war.thunderClapNext = false } diff --git a/sim/warrior/dps/dps_warrior_test.go b/sim/warrior/dps/dps_warrior_test.go index e0800764c6..c590f44202 100644 --- a/sim/warrior/dps/dps_warrior_test.go +++ b/sim/warrior/dps/dps_warrior_test.go @@ -113,15 +113,14 @@ var PlayerOptionsFury = &proto.Player_Warrior{ } var warriorOptions = &proto.Warrior_Options{ - StartingRage: 50, - UseRecklessness: true, - UseShatteringThrow: true, - Shout: proto.WarriorShout_WarriorShoutBattle, + StartingRage: 50, + UseRecklessness: true, + Shout: proto.WarriorShout_WarriorShoutBattle, } var FullConsumes = &proto.Consumes{ - Flask: proto.Flask_FlaskOfEndlessRage, - DefaultPotion: proto.Potions_PotionOfSpeed, - PrepopPotion: proto.Potions_PotionOfSpeed, - Food: proto.Food_FoodFishFeast, + // Flask: proto.Flask_FlaskOfEndlessRage, + // DefaultPotion: proto.Potions_PotionOfSpeed, + // PrepopPotion: proto.Potions_PotionOfSpeed, + // Food: proto.Food_FoodFishFeast, } diff --git a/sim/warrior/heroic_strike_cleave.go b/sim/warrior/heroic_strike_cleave.go index ff5f274245..7cd863dc9c 100644 --- a/sim/warrior/heroic_strike_cleave.go +++ b/sim/warrior/heroic_strike_cleave.go @@ -4,7 +4,7 @@ import ( "github.com/wowsims/sod/sim/core" ) -func (warrior *Warrior) registerHeroicStrikeSpell() *core.Spell { +func (warrior *Warrior) registerHeroicStrikeSpell() { damage := map[int32]float64{ 25: 44, 40: 80, @@ -19,7 +19,7 @@ func (warrior *Warrior) registerHeroicStrikeSpell() *core.Spell { 60: 11567, }[warrior.Level] - return warrior.RegisterSpell(core.SpellConfig{ + warrior.HeroicStrike = warrior.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolPhysical, ProcMask: core.ProcMaskMeleeMHSpecial, @@ -52,9 +52,10 @@ func (warrior *Warrior) registerHeroicStrikeSpell() *core.Spell { } }, }) + warrior.makeQueueSpellsAndAura(warrior.HeroicStrike) } -func (warrior *Warrior) registerCleaveSpell() *core.Spell { +func (warrior *Warrior) registerCleaveSpell() { flatDamageBonus := map[int32]float64{ 25: 5, 40: 18, @@ -73,7 +74,7 @@ func (warrior *Warrior) registerCleaveSpell() *core.Spell { numHits := min(targets, warrior.Env.GetNumTargets()) results := make([]*core.SpellResult, numHits) - return warrior.RegisterSpell(core.SpellConfig{ + warrior.Cleave = warrior.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolPhysical, ProcMask: core.ProcMaskMeleeMHSpecial, @@ -109,6 +110,7 @@ func (warrior *Warrior) registerCleaveSpell() *core.Spell { } }, }) + warrior.makeQueueSpellsAndAura(warrior.Cleave) } func (warrior *Warrior) makeQueueSpellsAndAura(srcSpell *core.Spell) *core.Spell { @@ -163,15 +165,3 @@ func (warrior *Warrior) TryHSOrCleave(sim *core.Simulation, mhSwingSpell *core.S return warrior.curQueuedAutoSpell } - -func (warrior *Warrior) RegisterHSOrCleave(useCleave bool) { - warrior.HeroicStrike = warrior.registerHeroicStrikeSpell() - warrior.Cleave = warrior.registerCleaveSpell() - - if useCleave { - warrior.Cleave = warrior.registerCleaveSpell() - warrior.hsOrCleaveQueueSpell = warrior.makeQueueSpellsAndAura(warrior.Cleave) - } else { - warrior.hsOrCleaveQueueSpell = warrior.makeQueueSpellsAndAura(warrior.HeroicStrike) - } -} diff --git a/sim/warrior/protection/protection_warrior.go b/sim/warrior/protection/protection_warrior.go index e7966c3b33..afe041dceb 100644 --- a/sim/warrior/protection/protection_warrior.go +++ b/sim/warrior/protection/protection_warrior.go @@ -1,7 +1,6 @@ package protection import ( - "github.com/wowsims/sod/sim/common" "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/warrior" @@ -27,21 +26,15 @@ func RegisterProtectionWarrior() { type ProtectionWarrior struct { *warrior.Warrior - Rotation *proto.ProtectionWarrior_Rotation - Options *proto.ProtectionWarrior_Options - - CustomRotation *common.CustomRotation + Options *proto.ProtectionWarrior_Options } func NewProtectionWarrior(character *core.Character, options *proto.Player) *ProtectionWarrior { warOptions := options.GetProtectionWarrior() war := &ProtectionWarrior{ - Warrior: warrior.NewWarrior(character, options.TalentsString, warrior.WarriorInputs{ - ShoutType: warOptions.Options.Shout, - }), - Rotation: warOptions.Rotation, - Options: warOptions.Options, + Warrior: warrior.NewWarrior(character, options.TalentsString, warrior.WarriorInputs{}), + Options: warOptions.Options, } rbo := core.RageBarOptions{ @@ -55,14 +48,7 @@ func NewProtectionWarrior(character *core.Character, options *proto.Player) *Pro rbo.OHSwingSpeed = oh.SwingSpeed } - war.EnableRageBar(rbo, func(sim *core.Simulation) { - if war.GCD.IsReady(sim) { - war.TryUseCooldowns(sim) - if war.GCD.IsReady(sim) { - war.doRotation(sim) - } - } - }) + war.EnableRageBar(rbo) war.EnableAutoAttacks(war, core.AutoAttackOptions{ MainHand: war.WeaponFromMainHand(war.DefaultMeleeCritMultiplier()), OffHand: war.WeaponFromOffHand(war.DefaultMeleeCritMultiplier()), @@ -87,17 +73,9 @@ func (war *ProtectionWarrior) GetWarrior() *warrior.Warrior { func (war *ProtectionWarrior) Initialize() { war.Warrior.Initialize() - war.RegisterHSOrCleave(false) war.RegisterShieldWallCD() war.RegisterShieldBlockCD() war.DefensiveStanceAura.BuildPhase = core.CharacterBuildPhaseTalents - - if !war.IsUsingAPL { - war.CustomRotation = war.makeCustomRotation() - } - if war.Options.UseShatteringThrow { - war.RegisterShatteringThrowCD() - } } func (war *ProtectionWarrior) Reset(sim *core.Simulation) { diff --git a/sim/warrior/protection/protection_warrior_test.go b/sim/warrior/protection/protection_warrior_test.go index 419ee26daf..9e42a6c23c 100644 --- a/sim/warrior/protection/protection_warrior_test.go +++ b/sim/warrior/protection/protection_warrior_test.go @@ -84,8 +84,7 @@ var DefaultTalents = "2500030023-302-053351225000012521030113321" var PlayerOptionsBasic = &proto.Player_ProtectionWarrior{ ProtectionWarrior: &proto.ProtectionWarrior{ - Options: warriorOptions, - Rotation: &proto.ProtectionWarrior_Rotation{}, + Options: warriorOptions, }, } @@ -95,6 +94,6 @@ var warriorOptions = &proto.ProtectionWarrior_Options{ } var FullConsumes = &proto.Consumes{ - BattleElixir: proto.BattleElixir_ElixirOfMastery, - GuardianElixir: proto.GuardianElixir_GiftOfArthas, + // BattleElixir: proto.BattleElixir_ElixirOfMastery, + // GuardianElixir: proto.GuardianElixir_GiftOfArthas, } diff --git a/sim/warrior/protection/rotation.go b/sim/warrior/protection/rotation.go deleted file mode 100644 index 961c6b9972..0000000000 --- a/sim/warrior/protection/rotation.go +++ /dev/null @@ -1,113 +0,0 @@ -package protection - -import ( - "github.com/wowsims/sod/sim/common" - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" - "github.com/wowsims/sod/sim/warrior" -) - -func (war *ProtectionWarrior) OnGCDReady(sim *core.Simulation) { - war.doRotation(sim) -} - -func (war *ProtectionWarrior) OnAutoAttack(sim *core.Simulation, spell *core.Spell) { - war.tryQueueHsCleave(sim) -} - -func (war *ProtectionWarrior) doRotation(sim *core.Simulation) { - war.trySwapToDefensive(sim) - war.CustomRotation.Cast(sim) - - // if we did nothing else, mark we intentionally did nothing here. - if war.GCD.IsReady(sim) { - war.DoNothing() - } -} - -func (war *ProtectionWarrior) tryQueueHsCleave(sim *core.Simulation) { - if war.ShouldQueueHSOrCleave(sim) { - war.QueueHSOrCleave(sim) - } -} - -func (war *ProtectionWarrior) shouldDemoShout(sim *core.Simulation) bool { - return war.ShouldDemoralizingShout(sim, war.CurrentTarget, - war.Rotation.DemoShoutChoice == proto.ProtectionWarrior_Rotation_DemoShoutChoiceFiller, - war.Rotation.DemoShoutChoice == proto.ProtectionWarrior_Rotation_DemoShoutChoiceMaintain) -} - -func (war *ProtectionWarrior) shouldThunderClap(sim *core.Simulation) bool { - return war.ShouldThunderClap(sim, war.CurrentTarget, - war.Rotation.ThunderClapChoice == proto.ProtectionWarrior_Rotation_ThunderClapChoiceOnCD, - war.Rotation.ThunderClapChoice == proto.ProtectionWarrior_Rotation_ThunderClapChoiceMaintain, - false) -} - -func (war *ProtectionWarrior) trySwapToDefensive(sim *core.Simulation) bool { - if !war.StanceMatches(warrior.DefensiveStance) && war.DefensiveStance.IsReady(sim) { - war.DefensiveStance.Cast(sim, nil) - return true - } - return false -} - -func (war *ProtectionWarrior) makeCustomRotation() *common.CustomRotation { - return common.NewCustomRotation(war.Rotation.CustomRotation, war.GetCharacter(), map[int32]common.CustomSpell{ - int32(proto.ProtectionWarrior_Rotation_Revenge): { - Spell: war.Revenge, - Condition: func(sim *core.Simulation) bool { - if !war.Rotation.PrioSslamOnShieldBlock { - return war.Revenge.CanCast(sim, war.CurrentTarget) - } - - if war.ShieldBlockAura.IsActive() { - return !war.ShieldSlam.CanCast(sim, war.CurrentTarget) && war.Revenge.CanCast(sim, war.CurrentTarget) - } else { - return war.Revenge.CanCast(sim, war.CurrentTarget) - } - }, - }, - int32(proto.ProtectionWarrior_Rotation_ShieldSlam): { - Spell: war.ShieldSlam, - Condition: func(sim *core.Simulation) bool { - if !war.Rotation.PrioSslamOnShieldBlock { - return war.ShieldSlam.CanCast(sim, war.CurrentTarget) - } - - if war.ShieldBlockAura.IsActive() { - return war.ShieldSlam.CanCast(sim, war.CurrentTarget) - } else { - return !war.Revenge.CanCast(sim, war.CurrentTarget) && war.ShieldSlam.CanCast(sim, war.CurrentTarget) - } - }, - }, - int32(proto.ProtectionWarrior_Rotation_Devastate): { - Spell: war.Devastate, - }, - int32(proto.ProtectionWarrior_Rotation_SunderArmor): { - Spell: war.SunderArmor, - }, - int32(proto.ProtectionWarrior_Rotation_DemoralizingShout): { - Spell: war.DemoralizingShout, - Condition: war.shouldDemoShout, - }, - int32(proto.ProtectionWarrior_Rotation_ThunderClap): { - Spell: war.ThunderClap, - Condition: war.shouldThunderClap, - }, - int32(proto.ProtectionWarrior_Rotation_Shout): { - Spell: war.Shout, - Condition: war.ShouldShout, - }, - int32(proto.ProtectionWarrior_Rotation_MortalStrike): { - Spell: war.MortalStrike, - }, - int32(proto.ProtectionWarrior_Rotation_ConcussionBlow): { - Spell: war.ConcussionBlow, - }, - int32(proto.ProtectionWarrior_Rotation_Shockwave): { - Spell: war.Shockwave, - }, - }) -} diff --git a/sim/warrior/shouts.go b/sim/warrior/shouts.go index 797f711dfb..22039f6ffa 100644 --- a/sim/warrior/shouts.go +++ b/sim/warrior/shouts.go @@ -4,7 +4,6 @@ import ( "time" "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" ) const ShoutExpirationThreshold = time.Second * 3 @@ -36,14 +35,8 @@ func (warrior *Warrior) makeShoutSpellHelper(actionID core.ActionID, allyAuras c }) } -func (warrior *Warrior) makeShoutSpell() *core.Spell { - battleShout := warrior.makeShoutSpellHelper(core.ActionID{SpellID: 47436}, warrior.NewAllyAuraArray(func(unit *core.Unit) *core.Aura { +func (warrior *Warrior) registerShouts() { + warrior.BattleShout = warrior.makeShoutSpellHelper(core.ActionID{SpellID: 47436}, warrior.NewAllyAuraArray(func(unit *core.Unit) *core.Aura { return core.BattleShoutAura(unit, warrior.Talents.ImprovedBattleShout, warrior.Talents.BoomingVoice, warrior.Level) })) - - if warrior.ShoutType == proto.WarriorShout_WarriorShoutBattle { - return battleShout - } else { - return nil - } } diff --git a/sim/warrior/warrior.go b/sim/warrior/warrior.go index 5b3b7edd8a..1af50dff5c 100644 --- a/sim/warrior/warrior.go +++ b/sim/warrior/warrior.go @@ -15,7 +15,6 @@ const ( var TalentTreeSizes = [3]int{31, 27, 27} type WarriorInputs struct { - ShoutType proto.WarriorShout StanceSnapshot bool } @@ -47,7 +46,8 @@ type Warrior struct { lastOverpowerProc time.Duration LastAMTick time.Duration - Shout *core.Spell + BattleShout *core.Spell + BattleStance *core.Spell DefensiveStance *core.Spell BerserkerStance *core.Spell @@ -73,12 +73,11 @@ type Warrior struct { ConcussionBlow *core.Spell RagingBlow *core.Spell - HeroicStrike *core.Spell - QuickStrike *core.Spell - Cleave *core.Spell - hsOrCleaveQueueSpell *core.Spell - curQueueAura *core.Aura - curQueuedAutoSpell *core.Spell + HeroicStrike *core.Spell + QuickStrike *core.Spell + Cleave *core.Spell + curQueueAura *core.Aura + curQueuedAutoSpell *core.Spell BattleStanceAura *core.Aura DefensiveStanceAura *core.Aura @@ -105,19 +104,20 @@ func (warrior *Warrior) Initialize() { warrior.AutoAttacks.MHConfig().CritMultiplier = warrior.autoCritMultiplier(mh) warrior.AutoAttacks.OHConfig().CritMultiplier = warrior.autoCritMultiplier(oh) - warrior.Shout = warrior.makeShoutSpell() - primaryTimer := warrior.NewTimer() overpowerRevengeTimer := warrior.NewTimer() warrior.reactionTime = time.Millisecond * 500 + warrior.registerShouts() warrior.registerStances() warrior.registerBerserkerRageSpell() warrior.registerBloodthirstSpell(primaryTimer) + warrior.registerCleaveSpell() warrior.registerDemoralizingShoutSpell() warrior.registerDevastateSpell() warrior.registerExecuteSpell() + warrior.registerHeroicStrikeSpell() warrior.registerMortalStrikeSpell(primaryTimer) warrior.registerOverpowerSpell(overpowerRevengeTimer) warrior.registerRevengeSpell(overpowerRevengeTimer) @@ -126,6 +126,7 @@ func (warrior *Warrior) Initialize() { warrior.registerThunderClapSpell() warrior.registerWhirlwindSpell() warrior.registerConcussionBlowSpell() + warrior.RegisterRendSpell() warrior.SunderArmor = warrior.newSunderArmorSpell(false) warrior.SunderArmorDevastate = warrior.newSunderArmorSpell(true) From 2352bfbfa27dd623d5dd115a42fab4d140deebd7 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 16:39:59 -0500 Subject: [PATCH 16/26] update main_test.go --- sim/web/main_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/sim/web/main_test.go b/sim/web/main_test.go index 47df39183e..48abd7c673 100644 --- a/sim/web/main_test.go +++ b/sim/web/main_test.go @@ -18,9 +18,6 @@ import ( var basicSpec = &proto.Player_ElementalShaman{ ElementalShaman: &proto.ElementalShaman{ - Rotation: &proto.ElementalShaman_Rotation{ - Type: proto.ElementalShaman_Rotation_Adaptive, - }, Options: &proto.ElementalShaman_Options{ Shield: proto.ShamanShield_WaterShield, }, From 686defc2119faf108ba43dcf7096495deebe940a Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 20:27:31 -0500 Subject: [PATCH 17/26] update cast.go --- sim/core/cast.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sim/core/cast.go b/sim/core/cast.go index 19c94525b4..24f1d1726a 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -199,15 +199,14 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { spell.Unit.SetGCDTimer(sim, sim.CurrentTime+effectiveTime) } - // TODO: Fix with removal of ChannelTime? - if (spell.CurCast.CastTime > 0 || spell.CurCast.ChannelTime > 0) && spell.Unit.Moving { + if (spell.CurCast.CastTime > 0) && spell.Unit.Moving { return spell.castFailureHelper(sim, false, "casting/channeling while moving not allowed!") } // TODO: Fix with removal of ChannelTime? // Non melee casts if spell.Flags.Matches(SpellFlagResetAttackSwing) && spell.Unit.AutoAttacks.enabled { - restartMeleeAt := sim.CurrentTime + spell.CurCast.CastTime + spell.CurCast.ChannelTime + restartMeleeAt := sim.CurrentTime + spell.CurCast.CastTime spell.Unit.AutoAttacks.StopMeleeUntil(sim, restartMeleeAt, false) } @@ -238,9 +237,7 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { } if !sim.Options.Interactive { - if spell.Unit.IsUsingAPL { - spell.Unit.Rotation.DoNextAction(sim) - } + spell.Unit.Rotation.DoNextAction(sim) } }, Target: target, From dfb109df2cf1b627a3d6f814f1093f75052f395a Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 20:34:06 -0500 Subject: [PATCH 18/26] add back TryUseCooldowns --- sim/core/major_cooldown.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sim/core/major_cooldown.go b/sim/core/major_cooldown.go index f1d84b07dc..335d44c61d 100644 --- a/sim/core/major_cooldown.go +++ b/sim/core/major_cooldown.go @@ -333,6 +333,36 @@ func (mcdm *majorCooldownManager) getFirstReadyMCD(sim *Simulation) *MajorCooldo return nil } +func (mcdm *majorCooldownManager) TryUseCooldowns(sim *Simulation) { + if sim.CurrentTime < mcdm.minReady { + return + } + +restart: + for _, mcd := range mcdm.majorCooldowns { + if !mcd.IsReady(sim) { + break + } + + if mcd.tryActivateInternal(sim, mcdm.character) { + if mcd.IsReady(sim) { + continue // activation failed + } + mcdm.sort() + + if mcd.Spell.DefaultCast.GCD > 0 { + // If the GCD was used, don't use any more MCDs until the next cycle so + // their durations aren't partially wasted. + break + } + + // many MCDs are off the GCD, so it makes sense to continue + goto restart + } + } + mcdm.minReady = mcdm.majorCooldowns[0].ReadyAt() +} + // This function should be called if the CD for a major cooldown changes outside // of the TryActivate() call. func (mcdm *majorCooldownManager) UpdateMajorCooldowns() { From 31a254a3cab34dbc03a748f0d53cb727e9f37a1f Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 20:40:09 -0500 Subject: [PATCH 19/26] fix some more sim errors --- sim/druid/feral/feral_test.go | 67 ++-------------------- sim/encounters/naxxramas/kelthuzad25_ai.go | 1 - sim/encounters/naxxramas/loatheb25_ai.go | 1 - sim/encounters/naxxramas/patchwerk10_ai.go | 1 - sim/encounters/naxxramas/patchwerk25_ai.go | 2 - sim/encounters/naxxramas/thaddius25_ai.go | 1 - 6 files changed, 5 insertions(+), 68 deletions(-) diff --git a/sim/druid/feral/feral_test.go b/sim/druid/feral/feral_test.go index 88c9984768..366424b129 100644 --- a/sim/druid/feral/feral_test.go +++ b/sim/druid/feral/feral_test.go @@ -80,25 +80,6 @@ var PlayerOptionsMonoCat = &proto.Player_FeralDruid{ LatencyMs: 100, AssumeBleedActive: true, }, - Rotation: &proto.FeralDruid_Rotation{ - RotationType: proto.FeralDruid_Rotation_SingleTarget, - BearWeaveType: proto.FeralDruid_Rotation_None, - UseRake: true, - UseBite: true, - MinCombosForRip: 5, - MinCombosForBite: 5, - BiteTime: 4.0, - MaintainFaerieFire: true, - BerserkBiteThresh: 25.0, - BerserkFfThresh: 15.0, - MaxFfDelay: 0.7, - MinRoarOffset: 24.0, - RipLeeway: 3, - SnekWeave: false, - FlowerWeave: false, - RaidTargets: 30, - PrePopOoc: true, - }, }, } @@ -109,25 +90,6 @@ var PlayerOptionsMonoCatNoBleed = &proto.Player_FeralDruid{ LatencyMs: 100, AssumeBleedActive: false, }, - Rotation: &proto.FeralDruid_Rotation{ - RotationType: proto.FeralDruid_Rotation_SingleTarget, - BearWeaveType: proto.FeralDruid_Rotation_None, - UseRake: true, - UseBite: true, - MinCombosForRip: 5, - MinCombosForBite: 5, - BiteTime: 4.0, - MaintainFaerieFire: true, - BerserkBiteThresh: 25.0, - BerserkFfThresh: 15.0, - MaxFfDelay: 0.7, - MinRoarOffset: 24.0, - RipLeeway: 3, - SnekWeave: false, - FlowerWeave: false, - RaidTargets: 30, - PrePopOoc: true, - }, }, } @@ -138,34 +100,15 @@ var PlayerOptionsFlowerCatAoe = &proto.Player_FeralDruid{ LatencyMs: 100, AssumeBleedActive: false, }, - Rotation: &proto.FeralDruid_Rotation{ - RotationType: proto.FeralDruid_Rotation_Aoe, - BearWeaveType: proto.FeralDruid_Rotation_None, - UseRake: true, - UseBite: true, - MinCombosForRip: 5, - MinCombosForBite: 5, - BiteTime: 4.0, - MaintainFaerieFire: true, - BerserkBiteThresh: 25.0, - BerserkFfThresh: 15.0, - MaxFfDelay: 0.7, - MinRoarOffset: 24.0, - RipLeeway: 3, - SnekWeave: false, - FlowerWeave: true, - RaidTargets: 30, - PrePopOoc: true, - }, }, } var FullConsumes = &proto.Consumes{ - BattleElixir: proto.BattleElixir_ElixirOfMajorAgility, - GuardianElixir: proto.GuardianElixir_ElixirOfMajorMageblood, - Food: proto.Food_FoodGrilledMudfish, - DefaultPotion: proto.Potions_HastePotion, - DefaultConjured: proto.Conjured_ConjuredDarkRune, + // BattleElixir: proto.BattleElixir_ElixirOfMajorAgility, + // GuardianElixir: proto.GuardianElixir_ElixirOfMajorMageblood, + // Food: proto.Food_FoodGrilledMudfish, + // DefaultPotion: proto.Potions_HastePotion, + // DefaultConjured: proto.Conjured_ConjuredDarkRune, } var P2GearDoubleArmorPenTrinkets = core.EquipmentSpecFromJsonString(` diff --git a/sim/encounters/naxxramas/kelthuzad25_ai.go b/sim/encounters/naxxramas/kelthuzad25_ai.go index ad5cde801b..46b43dcbeb 100644 --- a/sim/encounters/naxxramas/kelthuzad25_ai.go +++ b/sim/encounters/naxxramas/kelthuzad25_ai.go @@ -57,5 +57,4 @@ func (ai *KelThuzad25AI) Reset(*core.Simulation) { } func (ai *KelThuzad25AI) DoAction(sim *core.Simulation) { - ai.Target.DoNothing() } diff --git a/sim/encounters/naxxramas/loatheb25_ai.go b/sim/encounters/naxxramas/loatheb25_ai.go index dd5faed51f..b58e5d08ae 100644 --- a/sim/encounters/naxxramas/loatheb25_ai.go +++ b/sim/encounters/naxxramas/loatheb25_ai.go @@ -57,5 +57,4 @@ func (ai *Loatheb25AI) Reset(*core.Simulation) { } func (ai *Loatheb25AI) DoAction(sim *core.Simulation) { - ai.Target.DoNothing() } diff --git a/sim/encounters/naxxramas/patchwerk10_ai.go b/sim/encounters/naxxramas/patchwerk10_ai.go index 50a6cede4b..0a92ccb0b7 100644 --- a/sim/encounters/naxxramas/patchwerk10_ai.go +++ b/sim/encounters/naxxramas/patchwerk10_ai.go @@ -124,7 +124,6 @@ func (ai *Patchwerk10AI) registerFrenzySpell(target *core.Target) { func (ai *Patchwerk10AI) DoAction(sim *core.Simulation) { if ai.Target.CurrentTarget == nil { - ai.Target.DoNothing() return } diff --git a/sim/encounters/naxxramas/patchwerk25_ai.go b/sim/encounters/naxxramas/patchwerk25_ai.go index 8a7cbdc479..010c871ba0 100644 --- a/sim/encounters/naxxramas/patchwerk25_ai.go +++ b/sim/encounters/naxxramas/patchwerk25_ai.go @@ -125,11 +125,9 @@ func (ai *Patchwerk25AI) registerFrenzySpell(target *core.Target) { func (ai *Patchwerk25AI) DoAction(sim *core.Simulation) { if ai.Target.CurrentTarget == nil { - ai.Target.DoNothing() return } - ai.Target.DoNothing() // TODO: Re-enable Frenzy when we have a feature to flag for tank cooldown timing // Otherwise users get confused why the default settings say they die a lot... //if ai.Frenzy.IsReady(sim) && sim.GetRemainingDurationPercent() < 0.05 { diff --git a/sim/encounters/naxxramas/thaddius25_ai.go b/sim/encounters/naxxramas/thaddius25_ai.go index 7a99252bbd..6795e4edf5 100644 --- a/sim/encounters/naxxramas/thaddius25_ai.go +++ b/sim/encounters/naxxramas/thaddius25_ai.go @@ -57,5 +57,4 @@ func (ai *Thaddius25AI) Reset(*core.Simulation) { } func (ai *Thaddius25AI) DoAction(sim *core.Simulation) { - ai.Target.DoNothing() } From 3f5780bbd00937380e4402b3093ff9fc028c29ce Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 21:05:40 -0500 Subject: [PATCH 20/26] fix druid proto --- proto/druid.proto | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/proto/druid.proto b/proto/druid.proto index ac0f5ec4c7..61d9823305 100644 --- a/proto/druid.proto +++ b/proto/druid.proto @@ -89,7 +89,14 @@ message BalanceDruid { message FeralDruid { message Rotation { + bool maintain_faerie_fire = 1; + int32 min_combos_for_rip = 2; + float max_wait_time = 3; + float preroar_duration = 4; + bool precast_tigers_fury = 5; + bool use_shred_trick = 6; } + Rotation rotation = 1; message Options { UnitReference innervate_target = 1; @@ -101,6 +108,10 @@ message FeralDruid { message FeralTankDruid { message Rotation { + // Minimum rage to queue HS or Cleave. + int32 maul_rage_threshold = 1; + bool maintain_demoralizing_roar = 2; + double lacerate_time = 3; } message Options { @@ -118,4 +129,4 @@ message RestorationDruid { UnitReference innervate_target = 1; } Options options = 3; -} +} \ No newline at end of file From 7396c7cbfbcabfc71b770bd238c4010d53e57d4c Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 28 Jan 2024 22:39:59 -0500 Subject: [PATCH 21/26] fix hunter proto --- proto/hunter.proto | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proto/hunter.proto b/proto/hunter.proto index 2b22bd2882..a0825338bf 100644 --- a/proto/hunter.proto +++ b/proto/hunter.proto @@ -120,6 +120,21 @@ enum HunterRune { message Hunter { message Rotation { + enum RotationType { + UnknownType = 0; + SingleTarget = 1; + Aoe = 3; + Custom = 2; + } + RotationType type = 9; + + enum StingType { + NoSting = 0; + ScorpidSting = 1; + SerpentSting = 2; + } + StingType sting = 2; + bool multi_dot_serpent_sting = 3; } message Options { From 6259cc3a3ecc392119168e95192ea9c94e6f305f Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 29 Jan 2024 11:26:05 -0500 Subject: [PATCH 22/26] all files gone through --- package-lock.json | 24 ++ package.json | 2 + ui/balance_druid/presets.ts | 7 - ui/balance_druid/sim.ts | 5 - ui/core/components/exporters.ts | 148 +++++++- ui/core/components/importers.ts | 71 +++- .../individual_sim_ui/apl_values.ts | 10 - .../individual_sim_ui/cooldowns_picker.ts | 32 +- .../custom_rotation_picker.ts | 135 ------- .../components/individual_sim_ui/gear_tab.ts | 6 +- .../individual_sim_ui/rotation_tab.ts | 45 +-- .../individual_sim_ui/settings_tab.ts | 62 +-- ui/core/components/input_helpers.ts | 55 +-- ui/core/components/other_inputs.ts | 8 +- ui/core/components/totem_inputs.ts | 107 +++++- ui/core/individual_sim_ui.ts | 193 +++++----- ui/core/launched_sims.ts | 23 -- ui/core/player.ts | 355 +++++++----------- ui/core/preset_utils.ts | 8 - ui/core/proto_utils/utils.ts | 75 ---- ui/core/raid.ts | 41 -- ui/core/sim.ts | 11 + ui/elemental_shaman/apls/advanced.apl.json | 1 - ui/elemental_shaman/inputs.ts | 84 +---- ui/elemental_shaman/presets.ts | 21 -- ui/elemental_shaman/sim.ts | 22 -- ui/enhancement_shaman/inputs.ts | 196 ---------- ui/enhancement_shaman/presets.ts | 47 --- ui/enhancement_shaman/sim.ts | 4 - ui/feral_druid/inputs.ts | 2 +- ui/feral_tank_druid/inputs.ts | 6 - ui/healing_priest/inputs.ts | 35 -- ui/healing_priest/presets.ts | 29 -- ui/healing_priest/sim.ts | 4 - ui/holy_paladin/inputs.ts | 4 - ui/holy_paladin/presets.ts | 4 - ui/holy_paladin/sim.ts | 4 - ui/hunter/inputs.ts | 4 +- ui/hunter/presets.ts | 4 +- ui/hunter/sim.ts | 2 +- ui/protection_paladin/inputs.ts | 43 --- ui/protection_paladin/presets.ts | 23 -- ui/protection_paladin/sim.ts | 4 - ui/protection_warrior/inputs.ts | 66 +--- ui/protection_warrior/presets.ts | 28 +- ui/protection_warrior/sim.ts | 6 +- ui/raid/raid_stats.ts | 32 +- ui/restoration_druid/inputs.ts | 12 +- ui/restoration_druid/presets.ts | 4 - ui/restoration_druid/sim.ts | 4 - ui/restoration_shaman/inputs.ts | 52 --- ui/restoration_shaman/presets.ts | 21 -- ui/restoration_shaman/sim.ts | 4 - ui/retribution_paladin/inputs.ts | 141 +------ ui/retribution_paladin/presets.ts | 48 +-- ui/retribution_paladin/sim.ts | 21 -- ui/rogue/inputs.ts | 60 --- ui/rogue/presets.ts | 20 - ui/rogue/sim.ts | 85 ----- .../_custom_rotation_picker.scss | 33 -- .../individual_sim_ui/_settings_tab.scss | 1 - ui/shadow_priest/inputs.ts | 29 -- ui/shadow_priest/presets.ts | 6 - ui/shadow_priest/sim.ts | 5 - ui/warlock/inputs.ts | 4 - ui/warlock/presets.ts | 1 - ui/warlock/sim.ts | 1 - ui/warrior/inputs.ts | 16 +- ui/warrior/sim.ts | 2 - 69 files changed, 642 insertions(+), 2026 deletions(-) delete mode 100644 ui/core/components/individual_sim_ui/custom_rotation_picker.ts delete mode 100644 ui/scss/core/components/individual_sim_ui/_custom_rotation_picker.scss diff --git a/package-lock.json b/package-lock.json index 1c20f98592..0086547f48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@popperjs/core": "^2.11.6", "bootstrap": "5.2.3", + "pako": "^1.0.11", "tippy.js": "^6.3.7", "tsx-vanilla": "^1.0.0" }, @@ -18,6 +19,7 @@ "@protobuf-ts/runtime": "^2.0.4", "@types/bootstrap": "5.2.3", "@types/node": "^18.6.1", + "@types/pako": "^1.0.7", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.21.0", @@ -329,6 +331,12 @@ "integrity": "sha512-ATL4WLgr7/W40+Sp1WnNTSKbgVn6Pvhc/2RHAdt8fl6NsQyp4oPCi2eKcGOvA494bwf1K/W6nGgZ9TwDqvpjdw==", "dev": true }, + "node_modules/@types/pako": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -1933,6 +1941,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2880,6 +2893,12 @@ "integrity": "sha512-ATL4WLgr7/W40+Sp1WnNTSKbgVn6Pvhc/2RHAdt8fl6NsQyp4oPCi2eKcGOvA494bwf1K/W6nGgZ9TwDqvpjdw==", "dev": true }, + "@types/pako": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", + "dev": true + }, "@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -4020,6 +4039,11 @@ "p-limit": "^3.0.2" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 150e863957..eef15640a6 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@protobuf-ts/runtime": "^2.0.4", "@types/bootstrap": "5.2.3", "@types/node": "^18.6.1", + "@types/pako": "^1.0.7", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.21.0", @@ -26,6 +27,7 @@ "dependencies": { "@popperjs/core": "^2.11.6", "bootstrap": "5.2.3", + "pako": "^1.0.11", "tippy.js": "^6.3.7", "tsx-vanilla": "^1.0.0" } diff --git a/ui/balance_druid/presets.ts b/ui/balance_druid/presets.ts index 5583ef27c6..e9ebcb40cf 100644 --- a/ui/balance_druid/presets.ts +++ b/ui/balance_druid/presets.ts @@ -14,8 +14,6 @@ import { SavedTalents } from '../core/proto/ui.js'; import { BalanceDruid_Options as BalanceDruidOptions, - BalanceDruid_Rotation as BalanceDruidRotation, - BalanceDruid_Rotation_Type as RotationType } from '../core/proto/druid.js'; import * as PresetUtils from '../core/preset_utils.js'; @@ -41,11 +39,6 @@ export const BalanceTalents = { }), }; -export const DefaultRotation = BalanceDruidRotation.create({ - type: RotationType.Default, - playerLatency: 200, -}); - export const DefaultOptions = BalanceDruidOptions.create({ innervateTarget: UnitReference.create(), }); diff --git a/ui/balance_druid/sim.ts b/ui/balance_druid/sim.ts index 4bff7ff76d..46ece96ddc 100644 --- a/ui/balance_druid/sim.ts +++ b/ui/balance_druid/sim.ts @@ -16,7 +16,6 @@ import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.j import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs'; import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; import * as OtherInputs from '../core/components/other_inputs.js'; -import * as DruidInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { @@ -66,8 +65,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { }), // Default consumes settings. consumes: Presets.DefaultConsumes, - // Default rotation settings. - simpleRotation: Presets.DefaultRotation, // Default talents. talents: Presets.BalanceTalents.data, // Default spec-specific settings. @@ -83,8 +80,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ ], - // Inputs to include in the 'Rotation' section on the settings tab. - rotationInputs: DruidInputs.BalanceDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ ], diff --git a/ui/core/components/exporters.ts b/ui/core/components/exporters.ts index cb7308128a..b34f7aa702 100644 --- a/ui/core/components/exporters.ts +++ b/ui/core/components/exporters.ts @@ -1,5 +1,7 @@ +import pako from 'pako'; + import { IndividualSimUI } from '../individual_sim_ui'; -import { SimUI } from '../sim_ui'; +import { RaidSimRequest } from '../proto/api'; import { PseudoStat, Spec, @@ -9,18 +11,29 @@ import { IndividualSimSettings } from '../proto/ui'; import { classNames, raceNames } from '../proto_utils/names'; import { UnitStat } from '../proto_utils/stats'; import { specNames } from '../proto_utils/utils'; -import { downloadString, jsonStringifyWithFlattenedPaths } from '../utils'; +import { SimSettingCategories } from '../sim'; +import { SimUI } from '../sim_ui'; +import { EventID, TypedEvent } from '../typed_event'; +import { arrayEquals, downloadString, getEnumValues, jsonStringifyWithFlattenedPaths } from '../utils'; + import { BaseModal } from './base_modal'; -import { IndividualWowheadGearPlannerImporter } from './importers'; -import { RaidSimRequest } from '../proto/api'; +import { BooleanPicker } from './boolean_picker'; +import { IndividualLinkImporter, IndividualWowheadGearPlannerImporter } from './importers'; import * as Mechanics from '../constants/mechanics'; +interface ExporterOptions { + title: string, + header?: boolean, + allowDownload?: boolean, +} + export abstract class Exporter extends BaseModal { private readonly textElem: HTMLElement; + protected readonly changedEvent: TypedEvent = new TypedEvent(); - constructor(parent: HTMLElement, simUI: SimUI, title: string, allowDownload: boolean) { - super(parent, 'exporter', { title: title, footer: true }); + constructor(parent: HTMLElement, simUI: SimUI, options: ExporterOptions) { + super(parent, 'exporter', { title: options.title, header: options.header, footer: true }); this.body.innerHTML = ` @@ -30,7 +43,7 @@ export abstract class Exporter extends BaseModal { Copy to Clipboard - ${allowDownload ? ` + ${options.allowDownload ? `