diff --git a/outlaw.lua b/outlaw.lua new file mode 100644 index 0000000..73d2829 --- /dev/null +++ b/outlaw.lua @@ -0,0 +1,954 @@ +-- local Tinkr, Bastion = ... + +-- local OutlawModule = Bastion.Module:New('outlaw') +-- local Evaluator = Tinkr.Util.Evaluator +-- local Player = Bastion.UnitManager:Get('player') +-- local None = Bastion.UnitManager:Get('none') +-- local Target = Bastion.UnitManager:Get('target') + +-- local Stealth = Bastion.SpellBook:GetSpell(115191) +-- local Kick = Bastion.SpellBook:GetSpell(1766) +-- local CountTheOdds = Bastion.SpellBook:GetSpell(381982) +-- local Shadowmeld = Bastion.SpellBook:GetSpell(58984) +-- local ShadowDance = Bastion.SpellBook:GetSpell(185313) +-- local ShadowDanceAura = Bastion.SpellBook:GetSpell(185422) +-- local HiddenOpportunity = Bastion.SpellBook:GetSpell(383281) +-- local RollTheBones = Bastion.SpellBook:GetSpell(315508) +-- local FanTheHammer = Bastion.SpellBook:GetSpell(381846) +-- local ImprovedAmbush = Bastion.SpellBook:GetSpell(381620) +-- local SummarilyDispatched = Bastion.SpellBook:GetSpell(381990) +-- local BladeFlurry = Bastion.SpellBook:GetSpell(13877) +-- local KillingSpree = Bastion.SpellBook:GetSpell(51690) +-- local ArcaneTorrent = Bastion.SpellBook:GetSpell(25046) +-- local ArcanePulse = Bastion.SpellBook:GetSpell(260364) +-- local LightsJudgment = Bastion.SpellBook:GetSpell(255647) +-- local BagOfTricks = Bastion.SpellBook:GetSpell(312411) +-- local Sepsis = Bastion.SpellBook:GetSpell(385408) +-- local BetweenTheEyes = Bastion.SpellBook:GetSpell(315341) +-- local GhostlyStrike = Bastion.SpellBook:GetSpell(196937) +-- local Dreadblades = Bastion.SpellBook:GetSpell(343142) +-- local Subterfuge = Bastion.SpellBook:GetSpell(108208) +-- local EchoingReprimand = Bastion.SpellBook:GetSpell(385616) +-- local Ambush = Bastion.SpellBook:GetSpell(8676) +-- local KeepItRolling = Bastion.SpellBook:GetSpell(381989) +-- local Audacity = Bastion.SpellBook:GetSpell(381845) +-- local FindWeakness = Bastion.SpellBook:GetSpell(91023) +-- local PistolShot = Bastion.SpellBook:GetSpell(185763) +-- local Opportunity = Bastion.SpellBook:GetSpell(279876) +-- local OpportunityAura = Bastion.SpellBook:GetSpell(195627) +-- local GreenskinsWickers = Bastion.SpellBook:GetSpell(386823) +-- local QuickDraw = Bastion.SpellBook:GetSpell(196938) +-- local Weaponmaster = Bastion.SpellBook:GetSpell(200733) +-- local SinisterStrike = Bastion.SpellBook:GetSpell(193315) +-- local AdrenalineRush = Bastion.SpellBook:GetSpell(13750) +-- local ImprovedAdrenalineRush = Bastion.SpellBook:GetSpell(395422) +-- local BladeRush = Bastion.SpellBook:GetSpell(271877) +-- local MarkedForDeath = Bastion.SpellBook:GetSpell(137619) +-- local ThistleTea = Bastion.SpellBook:GetSpell(381623) +-- local PotionOfUnbridledFury = Bastion.SpellBook:GetSpell(169299) +-- local Bloodlust = Bastion.SpellBook:GetSpell(2825) +-- local BloodFury = Bastion.SpellBook:GetSpell(20572) +-- local Berserking = Bastion.SpellBook:GetSpell(26297) +-- local Fireblood = Bastion.SpellBook:GetSpell(265221) +-- local AncestralCall = Bastion.SpellBook:GetSpell(274738) +-- local SliceAndDice = Bastion.SpellBook:GetSpell(315496) +-- local SwiftSlasher = Bastion.SpellBook:GetSpell(381988) +-- local ColdBlood = Bastion.SpellBook:GetSpell(382245) +-- local Dispatch = Bastion.SpellBook:GetSpell(2098) +-- local Vanish = Bastion.SpellBook:GetSpell(1856) + +-- local Broadside = Bastion.SpellBook:GetSpell(193356) +-- local GrandMelee = Bastion.SpellBook:GetSpell(193358) +-- local SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) +-- local TrueBearing = Bastion.SpellBook:GetSpell(193359) +-- local LoadedDice = Bastion.SpellBook:GetSpell(256171) +-- local BuriedTreasure = Bastion.SpellBook:GetSpell(199600) +-- local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) + +-- local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) +-- local purge = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if unit:GetAuras():HasAnyStealableAura() then +-- purge = unit +-- return true +-- end +-- end) + +-- if purge == nil then +-- purge = None +-- end + +-- return purge +-- end) + +-- local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) +-- local purge = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if Player:InMelee(unit) and unit:IsInterruptible(5) and Player:IsFacing(unit) then +-- purge = unit +-- return true +-- end +-- end) + +-- if purge == nil then +-- purge = None +-- end + +-- return purge +-- end) + +-- local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) +-- local tank = nil + +-- Bastion.UnitManager:EnumFriends(function(unit) +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if unit:IsDead() then +-- return false +-- end + +-- if unit:IsTank() then +-- tank = unit +-- return true +-- end + +-- return false +-- end) + +-- if tank == nil then +-- tank = None +-- end + +-- return tank +-- end) + +-- local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) +-- local explosive = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if Player:InMelee(unit) and unit:GetID() == 120651 and Player:IsFacing(unit) then + +-- explosive = unit +-- return true +-- end +-- end) + +-- if explosive == nil then +-- explosive = None +-- end + +-- return explosive +-- end) + +-- local DefaultAPL = Bastion.APL:New('default') +-- local StealthAPL = Bastion.APL:New('stealth') +-- local CDsAPL = Bastion.APL:New('cds') +-- local FinishAPL = Bastion.APL:New('finish') +-- local BuildAPL = Bastion.APL:New('build') +-- local StealthCDsAPL = Bastion.APL:New('stealthcds') + +-- -- # Executed every time the actor is available. +-- -- # Restealth if possible (no vulnerable enemies in combat) +-- -- actions=stealth + +-- DefaultAPL:AddSpell( +-- Stealth:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and +-- not Player:IsAffectingCombat() and not IsMounted() +-- end +-- ):SetTarget(Player) +-- ) + +-- -- # Interrupt on cooldown to allow simming interactions with that +-- -- actions+=/kick +-- DefaultAPL:AddSpell( +-- Kick:CastableIf(function(self) +-- return self:IsKnownAndUsable() and KickTarget:Exists() and self:IsInRange(KickTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:IsFacing(Target) +-- end):SetTarget(KickTarget) +-- ) +-- -- # Checks if we are in an appropriate Stealth state for triggering the Count the Odds bonus +-- -- actions+=/variable,name=stealthed_cto,value=talent.count_the_odds&(stealthed.basic|buff.shadowmeld.up|buff.shadow_dance.up) +-- DefaultAPL:AddVariable( +-- 'stealthed_cto', +-- function() +-- return CountTheOdds:IsKnown() and (Player:GetAuras():FindMy(Stealth):IsUp() or +-- Player:GetAuras():FindMy(Shadowmeld):IsUp() or +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) +-- end +-- ) + +-- local function GetRTBCount() +-- local count = 0 + +-- if Player:GetAuras():FindMy(Broadside):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(GrandMelee):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(TrueBearing):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(LoadedDice):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(BuriedTreasure):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(RuthlessPrecision):IsUp() then +-- count = count + 1 +-- end + +-- return count +-- end + +-- -- # Roll the Bones Reroll Conditions +-- -- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity,value=rtb_buffs<2&(!buff.broadside.up&(!talent.fan_the_hammer|!buff.skull_and_crossbones.up)&!buff.true_bearing.up|buff.loaded_dice.up)|rtb_buffs=2&(buff.buried_treasure.up&buff.grand_melee.up|!buff.broadside.up&!buff.true_bearing.up&buff.loaded_dice.up) +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # Additional Reroll Conditions for Keep it Rolling or Count the Odds +-- -- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity&(talent.keep_it_rolling|talent.count_the_odds),value=variable.rtb_reroll|((rtb_buffs.normal=0&rtb_buffs.longer>=1)&!(buff.broadside.up&buff.true_bearing.up&buff.skull_and_crossbones.up)&!(buff.broadside.remains>39|buff.true_bearing.remains>39|buff.ruthless_precision.remains>39|buff.skull_and_crossbones.remains>39)) +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # With Hidden Opportunity, prioritize rerolling for Skull and Crossbones over everything else +-- -- actions+=/variable,name=rtb_reroll,if=talent.hidden_opportunity,value=!rtb_buffs.will_lose.skull_and_crossbones&(rtb_buffs.will_lose-rtb_buffs.will_lose.grand_melee)<2+buff.loaded_dice.up +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if HiddenOpportunity:IsKnown() then +-- return not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and +-- ((Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) - +-- (Player:GetAuras():FindMy(GrandMelee):IsUp() and 1 or 0)) < +-- 2 + (Player:GetAuras():FindMy(LoadedDice):IsUp() and 1 or 0) +-- end +-- end +-- ) + +-- -- # Avoid rerolls when we will not have time remaining on the fight or add wave to recoup the opportunity cost of the global +-- -- actions+=/variable,name=rtb_reroll,op=reset,if=!(raid_event.adds.remains>12|raid_event.adds.up&(raid_event.adds.in-raid_event.adds.remains)<6|target.time_to_die>12)|fight_remains<12 +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # Ensure we get full Ambush CP gains and aren't rerolling Count the Odds buffs away +-- -- actions+=/variable,name=ambush_condition,value=combo_points.deficit>=2+talent.improved_ambush+buff.broadside.up&energy>=50&(!talent.count_the_odds|buff.roll_the_bones.remains>=10) +-- DefaultAPL:AddVariable( +-- 'ambush_condition', +-- function() +-- return Player:GetComboPointsDeficit() >= 2 + (ImprovedAmbush:IsKnown() and 1 or 0) + +-- (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) and +-- Player:GetPower() >= 50 and +-- (not CountTheOdds:IsKnown() or +-- Player:GetAuras():FindMy(RollTheBones):GetRemainingTime() >= 10) +-- end +-- ) + +-- -- # Finish at 6 (5 with Summarily Dispatched talented) CP or CP Max-1, whichever is greater of the two +-- -- actions+=/variable,name=finish_condition,value=combo_points>=((cp_max_spend-1)=cp_max_spend +-- DefaultAPL:AddVariable( +-- 'finish_condition', +-- function() +-- return Player:GetComboPoints() >= +-- math.min(Player:GetComboPointsMax() - 1, 6 - (SummarilyDispatched:IsKnown() and 1 or 0)) or +-- Player:GetComboPoints() >= Player:GetComboPointsMax() +-- end +-- ) + +-- -- # With multiple targets, this variable is checked to decide whether some CDs should be synced with Blade Flurry +-- -- actions+=/variable,name=blade_flurry_sync,value=spell_targets.blade_flurry<2&raid_event.adds.in>20|buff.blade_flurry.remains>1+talent.killing_spree.enabled +-- DefaultAPL:AddVariable( +-- 'blade_flurry_sync', +-- function() +-- return Player:GetMeleeAttackers() < 2 and +-- (not Player:GetAuras():FindMy(BladeFlurry):IsUp() or +-- Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() > 1 + +-- (KillingSpree:IsKnown() and 1 or 0)) +-- end +-- ) + +-- -- # Higher priority Stealth list for Count the Odds or true Stealth/Vanish that will break in a single global +-- -- actions+=/call_action_list,name=stealth,if=stealthed.basic|buff.shadowmeld.up +-- DefaultAPL:AddAPL( +-- StealthAPL, +-- function() +-- return Player:IsStealthed() or Player:GetAuras():FindMy(Shadowmeld):IsUp() +-- end +-- ) + +-- -- actions+=/call_action_list,name=cds +-- DefaultAPL:AddAPL( +-- CDsAPL, +-- function() +-- return true +-- end +-- ) + +-- -- # Lower priority Stealth list for Shadow Dance +-- -- actions+=/call_action_list,name=stealth,if=variable.stealthed_cto +-- DefaultAPL:AddAPL( +-- StealthAPL, +-- function() +-- return DefaultAPL:GetVariable('stealthed_cto') +-- end +-- ) + +-- -- actions+=/run_action_list,name=finish,if=variable.finish_condition +-- DefaultAPL:AddAPL( +-- FinishAPL, +-- function() +-- return DefaultAPL:GetVariable('finish_condition') +-- end +-- ) + +-- -- actions+=/call_action_list,name=build +-- DefaultAPL:AddAPL( +-- BuildAPL, +-- function() +-- return true +-- end +-- ) + +-- -- actions+=/arcane_torrent,if=energy.base_deficit>=15+energy.regen +-- DefaultAPL:AddSpell( +-- ArcaneTorrent:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetPowerDeficit() >= 15 + Player:GetPowerRegen() +-- end +-- ):SetTarget(Target) + +-- ) + +-- -- actions+=/arcane_pulse +-- DefaultAPL:AddSpell( +-- ArcanePulse:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions+=/lights_judgment +-- DefaultAPL:AddSpell( +-- LightsJudgment:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions+=/bag_of_tricks +-- DefaultAPL:AddSpell( +-- BagOfTricks:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Builders +-- -- actions.build=sepsis,target_if=max:target.time_to_die*debuff.between_the_eyes.up,if=target.time_to_die>11&debuff.between_the_eyes.up|fight_remains<11 +-- BuildAPL:AddSpell( +-- Sepsis:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and ((Target:TimeToDie() > 11 and +-- Target:GetAuras():FindMy(BetweenTheEyes):IsUp()) or +-- Player:TimeToDie() < 11) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/ghostly_strike,if=debuff.ghostly_strike.remains<=3&(spell_targets.blade_flurry<=2|buff.dreadblades.up)&!buff.subterfuge.up&target.time_to_die>=5 +-- BuildAPL:AddSpell( +-- GhostlyStrike:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Target:GetAuras():FindMy(GhostlyStrike):GetRemainingTime() <= 3 and +-- (Player:GetMeleeAttackers() <= 2 or Player:GetAuras():FindMy(Dreadblades):IsUp()) and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and Target:TimeToDie() >= 5 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/echoing_reprimand,if=!buff.dreadblades.up +-- BuildAPL:AddSpell( +-- EchoingReprimand:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Dreadblades):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # High priority Ambush line to apply Find Weakness or consume Audacity/Sepsis buff before Pistol Shot +-- -- actions.build+=/ambush,if=(talent.hidden_opportunity|talent.keep_it_rolling)&(buff.audacity.up|buff.sepsis_buff.up|buff.subterfuge.up&cooldown.keep_it_rolling.ready)|talent.find_weakness&debuff.find_weakness.down +-- BuildAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and ((HiddenOpportunity:IsKnown() or KeepItRolling:IsKnown()) and +-- (Player:GetAuras():FindMy(Audacity):IsUp() or Player:GetAuras():FindMy(Sepsis):IsUp() or +-- (Player:GetAuras():FindMy(Subterfuge):IsUp() and KeepItRolling:OnCooldown())) or +-- (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # With Audacity + Hidden Opportunity + Fan the Hammer, use Pistol Shot to proc Audacity any time Ambush is not available +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&talent.audacity&talent.hidden_opportunity&buff.opportunity.up&!buff.audacity.up&!buff.subterfuge.up&!buff.shadow_dance.up +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and Audacity:IsKnown() and +-- HiddenOpportunity:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and not Player:GetAuras():FindMy(Audacity):IsUp() and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and not Player:GetAuras():FindMy(ShadowDance):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Use Greenskins Wickers buff immediately with Opportunity unless running Fan the Hammer +-- -- actions.build+=/pistol_shot,if=buff.greenskins_wickers.up&(!talent.fan_the_hammer&buff.opportunity.up|buff.greenskins_wickers.remains<1.5) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(GreenskinsWickers):IsUp() and +-- (not FanTheHammer:IsKnown() and Player:GetAuras():FindMy(OpportunityAura):IsUp() or +-- Player:GetAuras():FindMy(GreenskinsWickers):GetRemainingTime() < 1.5) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- const int stacks = 1 + as( p()->talent.outlaw.fan_the_hammer->effectN( 1 ).base_value() ); +-- local function MaxOpportunity() +-- return 1 + (FanTheHammer:IsKnown() and 1 or 0) +-- end + +-- -- # With Fan the Hammer, consume Opportunity at max stacks or if we will get max 4+ CP and Dreadblades is not up +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&(buff.opportunity.stack>=buff.opportunity.max_stack|buff.opportunity.remains<2) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- ( +-- Player:GetAuras():FindMy(OpportunityAura):GetCount() >= +-- MaxOpportunity() +-- or +-- Player:GetAuras():FindMy(OpportunityAura):GetRemainingTime() < 2) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&combo_points.deficit>((1+talent.quick_draw)*talent.fan_the_hammer.rank)&!buff.dreadblades.up&(!talent.hidden_opportunity|!buff.subterfuge.up&!buff.shadow_dance.up) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- Player:GetComboPointsDeficit() > ((1 + (QuickDraw:IsKnown() and 1 or 0)) * 1) and +-- not Player:GetAuras():FindMy(Dreadblades):IsUp() and +-- (not HiddenOpportunity:IsKnown() or not Player:GetAuras():FindMy(Subterfuge):IsUp() and +-- not Player:GetAuras():FindMy(ShadowDance):IsUp()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/pool_resource,for_next=1 +-- -- actions.build+=/ambush,if=talent.hidden_opportunity|talent.find_weakness&debuff.find_weakness.down +-- BuildAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and (HiddenOpportunity:IsKnown() or +-- (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Use Pistol Shot with Opportunity if Combat Potency won't overcap energy, when it will exactly cap CP, or when using Quick Draw +-- -- actions.build+=/pistol_shot,if=!talent.fan_the_hammer&buff.opportunity.up&(energy.base_deficit>energy.regen*1.5|!talent.weaponmaster&combo_points.deficit<=1+buff.broadside.up|talent.quick_draw.enabled|talent.audacity.enabled&!buff.audacity.up) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- (Player:GetPowerDeficit() > Player:GetPowerRegen() * 1.5 or +-- (not Weaponmaster:IsKnown() and +-- Player:GetComboPointsDeficit() <= 1 + (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0)) or +-- QuickDraw:IsKnown() or +-- (Audacity:IsKnown() and not Player:GetAuras():FindMy(Audacity):IsUp())) +-- end +-- ):SetTarget(Target) +-- ) + + +-- -- actions.build+=/sinister_strike +-- BuildAPL:AddSpell( +-- SinisterStrike:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Cooldowns +-- -- actions.cds=adrenaline_rush,if=!buff.adrenaline_rush.up&(!talent.improved_adrenaline_rush|combo_points<=2) +-- CDsAPL:AddSpell( +-- AdrenalineRush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(AdrenalineRush):IsUp() and +-- (not ImprovedAdrenalineRush:IsKnown() or Player:GetComboPoints() <= 2) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/blade_flurry,if=spell_targets>=2&buff.blade_flurry.remains= 2 and +-- Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() < Player:GetGCD() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/roll_the_bones,if=buff.dreadblades.down&(rtb_buffs.total=0|variable.rtb_reroll) +-- CDsAPL:AddSpell( +-- RollTheBones:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Dreadblades):IsDown() and +-- (GetRTBCount() == 0 or +-- DefaultAPL:GetVariable("rtb_reroll")) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/keep_it_rolling,if=!variable.rtb_reroll&(buff.broadside.up+buff.true_bearing.up+buff.skull_and_crossbones.up+buff.ruthless_precision.up)>2&(buff.shadow_dance.down|rtb_buffs>=6) +-- CDsAPL:AddSpell( +-- RollTheBones:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not DefaultAPL:GetVariable("rtb_reroll") and +-- ((Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(TrueBearing):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(RuthlessPrecision):IsUp() and 1 or 0)) > 2 and +-- (Player:GetAuras():FindMy(ShadowDance):IsDown() or +-- GetRTBCount() >= 6) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/blade_rush,if=variable.blade_flurry_sync&!buff.dreadblades.up&(energy.base_time_to_max>4+stealthed.rogue-spell_targets%3) +-- CDsAPL:AddSpell( +-- BladeRush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and +-- not Player:GetAuras():FindMy(Dreadblades):IsUp() and +-- (Player:GetTimeToPowerPercent() > 4 + (Player:GetAuras():FindMy(Subterfuge):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(ShadowDance):IsUp() and 1 or 0) - Target:GetMeleeAttackers() % 3) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/call_action_list,name=stealth_cds,if=!stealthed.all|talent.count_the_odds&!variable.stealthed_cto +-- CDsAPL:AddAPL( +-- StealthCDsAPL, +-- function() +-- return not Player:GetAuras():FindMy(Stealth):IsUp() or +-- (CountTheOdds:IsKnown() and not DefaultAPL:GetVariable("stealthed_cto")) +-- end +-- ) + +-- -- actions.cds+=/dreadblades,if=!(variable.stealthed_cto|stealthed.basic|talent.hidden_opportunity&stealthed.rogue)&combo_points<=2&(!talent.marked_for_death|!cooldown.marked_for_death.ready)&target.time_to_die>=10 +-- CDsAPL:AddSpell( +-- Dreadblades:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and +-- not (DefaultAPL:GetVariable("stealthed_cto") or Player:GetAuras():FindMy(Stealth):IsUp() or +-- (HiddenOpportunity:IsKnown() and Player:GetAuras():FindMy(Subterfuge):IsUp())) and +-- Player:GetComboPoints() <= 2 and +-- (not MarkedForDeath:IsKnown() or not MarkedForDeath:CooldownUp()) and +-- Target:TimeToDie() >= 10 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or without any CP. +-- -- actions.cds+=/marked_for_death,line_cd=1.5,target_if=min:target.time_to_die,if=raid_event.adds.up&(target.time_to_die=cp_max_spend-1)&!buff.dreadblades.up + +-- -- # If no adds will die within the next 30s, use MfD on boss without any CP. +-- -- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend-1&!buff.dreadblades.up + +-- -- actions.cds+=/thistle_tea,if=!buff.thistle_tea.up&(energy.base_deficit>=100|fight_remains= 100 or Target:TimeToDie() < ThistleTea:Charges() * 6) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/killing_spree,if=variable.blade_flurry_sync&!stealthed.rogue&debuff.between_the_eyes.up&energy.base_time_to_max>4 +-- CDsAPL:AddSpell( +-- KillingSpree:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and +-- Target:GetAuras():FindMy(BetweenTheEyes):IsUp() and +-- Player:GetTimeToPowerPercent() > 4 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/shadowmeld,if=!stealthed.all&(talent.count_the_odds&variable.finish_condition|!talent.weaponmaster.enabled&variable.ambush_condition) +-- CDsAPL:AddSpell( +-- Shadowmeld:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and +-- (CountTheOdds:IsKnown() and DefaultAPL:GetVariable("finish_condition") or +-- not Weaponmaster:IsKnown() and DefaultAPL:GetVariable("ambush_condition")) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.adrenaline_rush.up +-- -- CDsAPL:AddItem( +-- -- PotionOfUnbridledFury:CastableIf( +-- -- function(self) +-- -- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Bloodlust):IsUp() or Target:TimeToDie() < 30 or +-- -- Player:GetAuras():FindMy(AdrenalineRush):IsUp() +-- -- end +-- -- ):SetTarget(Target) +-- -- ) + +-- -- actions.cds+=/blood_fury +-- CDsAPL:AddSpell( +-- BloodFury:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/berserking +-- CDsAPL:AddSpell( +-- Berserking:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/fireblood +-- CDsAPL:AddSpell( +-- Fireblood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/ancestral_call +-- CDsAPL:AddSpell( +-- AncestralCall:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Default conditions for usable items. +-- -- actions.cds+=/use_item,name=manic_grieftorch,if=!stealthed.all&!buff.adrenaline_rush.up|fight_remains<5 +-- -- actions.cds+=/use_item,name=stormeaters_boon,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<10 +-- -- actions.cds+=/use_item,name=windscar_whetstone,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<7 +-- -- actions.cds+=/use_items,slots=trinket1,if=debuff.between_the_eyes.up|trinket.1.has_stat.any_dps|fight_remains<=20 +-- -- actions.cds+=/use_items,slots=trinket2,if=debuff.between_the_eyes.up|trinket.2.has_stat.any_dps|fight_remains<=20 + +-- -- # Finishers BtE to keep the Crit debuff up, if RP is up, or for Greenskins, unless the target is about to die. +-- -- actions.finish=between_the_eyes,if=target.time_to_die>3&(debuff.between_the_eyes.remains<4|talent.greenskins_wickers&!buff.greenskins_wickers.up|!talent.greenskins_wickers&buff.ruthless_precision.up) +-- FinishAPL:AddSpell( +-- BetweenTheEyes:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Target:TimeToDie() > 3 and +-- (Target:GetAuras():FindMy(BetweenTheEyes):GetRemainingTime() < 4 or +-- GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(GreenskinsWickers):IsDown() or +-- not GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(RuthlessPrecision):IsUp()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/slice_and_dice,if=buff.slice_and_dice.remains=cp_max_spend) +-- FinishAPL:AddSpell( +-- SliceAndDice:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < Target:TimeToDie() and +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 6 and +-- (not SwiftSlasher:IsKnown() or Player:GetComboPoints() >= Player:GetComboPointsMax()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/cold_blood +-- FinishAPL:AddSpell( +-- ColdBlood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/dispatch +-- FinishAPL:AddSpell( +-- Dispatch:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Stealth +-- -- actions.stealth=blade_flurry,if=talent.subterfuge&talent.hidden_opportunity&spell_targets>=2&!buff.blade_flurry.up +-- StealthAPL:AddSpell( +-- BladeFlurry:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Subterfuge:IsKnown() and HiddenOpportunity:IsKnown() and +-- Target:GetMeleeAttackers() >= 2 and +-- not Player:GetAuras():FindMy(BladeFlurry):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/cold_blood,if=variable.finish_condition +-- StealthAPL:AddSpell( +-- ColdBlood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/dispatch,if=variable.finish_condition +-- StealthAPL:AddSpell( +-- Dispatch:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/ambush,if=variable.stealthed_cto|stealthed.basic&talent.find_weakness&!debuff.find_weakness.up|talent.hidden_opportunity +-- StealthAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and (DefaultAPL:GetVariable('stealthed_cto') or +-- Player:IsStealthed() and FindWeakness:IsKnown() and not Target:GetAuras():FindMy(FindWeakness):IsUp() or +-- HiddenOpportunity:IsKnown()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Stealth Cooldowns +-- -- actions.stealth_cds=variable,name=vanish_condition,value=talent.hidden_opportunity|!talent.shadow_dance|!cooldown.shadow_dance.ready +-- StealthAPL:AddVariable( +-- 'vanish_condition', +-- function() +-- return HiddenOpportunity:IsKnown() or not ShadowDance:IsKnown() or not ShadowDance:CooldownUp() +-- end +-- ) + +-- -- actions.stealth_cds+=/variable,name=vanish_opportunity_condition,value=!talent.shadow_dance&talent.fan_the_hammer.rank+talent.quick_draw+talent.audacity120&(variable.finish_condition|talent.hidden_opportunity)) +-- StealthAPL:AddSpell( +-- ShadowDance:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and KeepItRolling:IsKnown() and +-- StealthAPL:GetVariable('shadow_dance_condition') and +-- (KeepItRolling:GetCooldownRemaining() <= 30 or +-- KeepItRolling:GetCooldownRemaining() > 120 and +-- (DefaultAPL:GetVariable('finish_condition') or HiddenOpportunity:IsKnown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- OutlawModule:Sync(function() +-- print(BetweenTheEyes:IsKnownAndUsable()) +-- DefaultAPL:Execute() +-- end) + +-- Bastion:Register(OutlawModule) diff --git a/restodruid.lua b/restodruid.lua new file mode 100644 index 0000000..272b585 --- /dev/null +++ b/restodruid.lua @@ -0,0 +1,758 @@ +local Tinkr, Bastion = ... + +local RestoModule = Bastion.Module:New('resto_druid') +local Evaluator = Tinkr.Util.Evaluator +local Player = Bastion.UnitManager:Get('player') +local None = Bastion.UnitManager:Get('none') +local Target = Bastion.UnitManager:Get('target') + +local AnomalyDetectionMarkI = Bastion.SpellBook:GetSpell(382499) +local AutoAttack = Bastion.SpellBook:GetSpell(6603) +local MechanismBypass = Bastion.SpellBook:GetSpell(382501) +local OverloadElementalDeposit = Bastion.SpellBook:GetSpell(388213) +local ReviveBattlePets = Bastion.SpellBook:GetSpell(125439) +local WarStomp = Bastion.SpellBook:GetSpell(20549) +local ArmorSkills = Bastion.SpellBook:GetSpell(76275) +local Brawn = Bastion.SpellBook:GetSpell(154743) +local Cultivation = Bastion.SpellBook:GetSpell(20552) +local Endurance = Bastion.SpellBook:GetSpell(20550) +local Languages = Bastion.SpellBook:GetSpell(79746) +local MasterRiding = Bastion.SpellBook:GetSpell(90265) +local NatureResistance = Bastion.SpellBook:GetSpell(20551) +local WeaponSkills = Bastion.SpellBook:GetSpell(76300) +local ActivateEmpowerment = Bastion.SpellBook:GetSpell(357857) +local BlessingofOhnara = Bastion.SpellBook:GetSpell(384522) +local BronzeTimelock = Bastion.SpellBook:GetSpell(374990) +local ChampionAbility = Bastion.SpellBook:GetSpell(356550) +local CenarionWard = Bastion.SpellBook:GetSpell(102351) +local CombatAlly = Bastion.SpellBook:GetSpell(211390) +local ConstructAbility = Bastion.SpellBook:GetSpell(347013) +local CovenantAbility = Bastion.SpellBook:GetSpell(313347) +local GarrisonAbility = Bastion.SpellBook:GetSpell(161691) +local HeartEssence = Bastion.SpellBook:GetSpell(296208) +local HuntingCompanion = Bastion.SpellBook:GetSpell(376280) +local SanityRestorationOrb = Bastion.SpellBook:GetSpell(314955) +local SignatureAbility = Bastion.SpellBook:GetSpell(326526) +local SkywardAscent = Bastion.SpellBook:GetSpell(372610) +local SummonPocopoc = Bastion.SpellBook:GetSpell(360078) +local SurgeForward = Bastion.SpellBook:GetSpell(372608) +local Throw = Bastion.SpellBook:GetSpell(385265) +local VenthyrAbility = Bastion.SpellBook:GetSpell(315594) +local WartimeAbility = Bastion.SpellBook:GetSpell(264739) +local WhirlingSurge = Bastion.SpellBook:GetSpell(361584) +local PocopocZoneAbilitySkill = Bastion.SpellBook:GetSpell(363942) +local DragonridingBasics = Bastion.SpellBook:GetSpell(376777) +local LiftOff = Bastion.SpellBook:GetSpell(383363) +local ThrilloftheSkies = Bastion.SpellBook:GetSpell(383366) +local Vigor = Bastion.SpellBook:GetSpell(383359) +local WindsoftheIsles = Bastion.SpellBook:GetSpell(373586) +local Barkskin = Bastion.SpellBook:GetSpell(22812) +local BearForm = Bastion.SpellBook:GetSpell(5487) +local CatForm = Bastion.SpellBook:GetSpell(768) +local Cyclone = Bastion.SpellBook:GetSpell(33786) +local EntanglingRoots = Bastion.SpellBook:GetSpell(339) +local FerociousBite = Bastion.SpellBook:GetSpell(22568) +local FrenziedRegeneration = Bastion.SpellBook:GetSpell(22842) +local Growl = Bastion.SpellBook:GetSpell(6795) +local Innervate = Bastion.SpellBook:GetSpell(29166) +local Mangle = Bastion.SpellBook:GetSpell(33917) +local MarkoftheWild = Bastion.SpellBook:GetSpell(1126) +local Moonfire = Bastion.SpellBook:GetSpell(8921) +local MoonfireAura = Bastion.SpellBook:GetSpell(164812) +local Prowl = Bastion.SpellBook:GetSpell(5215) +local Rebirth = Bastion.SpellBook:GetSpell(20484) +local Regrowth = Bastion.SpellBook:GetSpell(8936) +local Rejuvenation = Bastion.SpellBook:GetSpell(774) +local RejuvenationAura = Bastion.SpellBook:GetSpell(25299) +local Revive = Bastion.SpellBook:GetSpell(50769) +local Rip = Bastion.SpellBook:GetSpell(1079) +local Shred = Bastion.SpellBook:GetSpell(5221) +local Soothe = Bastion.SpellBook:GetSpell(2908) +local StampedingRoar = Bastion.SpellBook:GetSpell(106898) +local Sunfire = Bastion.SpellBook:GetSpell(93402) +local SunfireAura = Bastion.SpellBook:GetSpell(164815) +local Swiftmend = Bastion.SpellBook:GetSpell(18562) +local TeleportMoonglade = Bastion.SpellBook:GetSpell(18960) +local Thrash = Bastion.SpellBook:GetSpell(106832) +local TigerDash = Bastion.SpellBook:GetSpell(252216) +local TravelForm = Bastion.SpellBook:GetSpell(783) +local UrsolsVortex = Bastion.SpellBook:GetSpell(102793) +local WildGrowth = Bastion.SpellBook:GetSpell(48438) +local Wrath = Bastion.SpellBook:GetSpell(5176) +local AquaticForm = Bastion.SpellBook:GetSpell(276012) +local FlightForm = Bastion.SpellBook:GetSpell(276029) +local TigerDash = Bastion.SpellBook:GetSpell(252216) +local Efflorescence = Bastion.SpellBook:GetSpell(145205) +local IncarnationTreeofLife = Bastion.SpellBook:GetSpell(33891) +local Ironbark = Bastion.SpellBook:GetSpell(102342) +local Lifebloom = Bastion.SpellBook:GetSpell(33763) +local LifebloomAura = Bastion.SpellBook:GetSpell(188550) +local NaturesCure = Bastion.SpellBook:GetSpell(88423) +local NaturesSwiftness = Bastion.SpellBook:GetSpell(132158) +local Revitalize = Bastion.SpellBook:GetSpell(212040) +local Tranquility = Bastion.SpellBook:GetSpell(740) +local MasteryHarmony = Bastion.SpellBook:GetSpell(77495) +local Moonfire = Bastion.SpellBook:GetSpell(8921) +local Wrath = Bastion.SpellBook:GetSpell(5176) +local BearForm = Bastion.SpellBook:GetSpell(5487) +local AdaptiveSwarm = Bastion.SpellBook:GetSpell(391888) +local AdaptiveSwarmBuff = Bastion.SpellBook:GetSpell(391891) +local ClearCasting = Bastion.SpellBook:GetSpell(16870) +local ConvokeTheSpirits = Bastion.SpellBook:GetSpell(391528) +local Flourish = Bastion.SpellBook:GetSpell(197721) +local SoulOfTheForest = Bastion.SpellBook:GetSpell(114108) +local Bursting = Bastion.SpellBook:GetSpell(240443) +local Rake = Bastion.SpellBook:GetSpell(1822) +local RakeAura = Bastion.SpellBook:GetSpell(155722) +local Starsurge = Bastion.SpellBook:GetSpell(197626) +local NaturesVigil = Bastion.SpellBook:GetSpell(124974) +local SpringBlossoms = Bastion.SpellBook:GetSpell(207386) +local RakeDebuff = Bastion.SpellBook:GetSpell(155722) + + +local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not Player:CanSee(unit) then + return false + end + + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end) + + if not lowest then + lowest = Player + end + + return lowest + end) + +local DispelTarget = Bastion.UnitManager:CreateCustomUnit('dispel', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and + unit:GetAuras():HasAnyDispelableAura(NaturesCure) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + end) + + if lowest == nil then + lowest = None + end + + return lowest + end) + +local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) + local purge = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and + unit:GetAuras():HasAnyStealableAura() then + purge = unit + return true + end + end) + + if purge == nil then + purge = None + end + + return purge + end) + +local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) + local tank = nil + + Bastion.UnitManager:EnumFriends(function(unit) + if Player:GetDistance(unit) > 40 then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if unit:IsDead() then + return false + end + + if unit:IsTank() then + tank = unit + return true + end + + return false + end) + + if tank == nil then + tank = Player + end + + return tank + end) + +local RejuvUnit = Bastion.UnitManager:CreateCustomUnit('rejuv', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and + ( + not unit:GetAuras():FindMy(Rejuvenation):IsUp() or + unit:GetAuras():FindMy(Rejuvenation):GetRemainingTime() <= 3.6) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + end) + + + if lowest == nil then + lowest = Player + end + + return lowest + end) + +local SwiftmendUnit = Bastion.UnitManager:CreateCustomUnit('swiftmend', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if ( + Player:CanSee(unit) and ( + (unit:GetAuras():FindMy(Regrowth):IsUp()) + or + ( + unit:GetAuras():FindMy(Rejuvenation):IsUp() and + not unit:GetAuras():FindMy(WildGrowth):IsUp()) + ) + ) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + end) + + + if lowest == nil then + lowest = None + end + + return lowest + end) + +local WildGrowthUnit = Bastion.UnitManager:CreateCustomUnit('wildgrowth', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if Player:CanSee(unit) and ( + ( + Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and + ( + Player:GetAuras():FindMy(SoulOfTheForest):GetRemainingTime() <= 5 or + unit:GetPartyHPAround(30, 90) >= 2)) or + (unit:GetPartyHPAround(30, 90) >= 3 or unit:GetPartyHPAround(30, 85) >= 2)) + then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + end) + + + if lowest == nil then + lowest = None + end + + return lowest + end) + +local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) + local explosive = nil + + Bastion.ObjectManager.explosives:each(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) <= 40 then + explosive = unit + return true + end + end) + + if explosive == nil then + explosive = None + end + + return explosive + end) + +local RakeTarget = Bastion.UnitManager:CreateCustomUnit('rake', function(unit) + local rakeTarget = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and unit:InMelee(Player) and + Player:IsFacing(unit) and + ( + not unit:GetAuras():FindMy(RakeDebuff):IsUp() or + unit:GetAuras():FindMy(RakeDebuff):GetRemainingTime() <= 3.6) then + rakeTarget = unit + end + end) + + + if rakeTarget == nil then + rakeTarget = None + end + + return rakeTarget + end) + +local MoonfireTarget = Bastion.UnitManager:CreateCustomUnit('moonfire', function(unit) + local moonfireTarget = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and + ( + not unit:GetAuras():FindMy(MoonfireAura):IsUp() or + unit:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 3.6) then + moonfireTarget = unit + end + end) + + if moonfireTarget == nil then + moonfireTarget = None + end + + return moonfireTarget + end) + +local SunfireTarget = Bastion.UnitManager:CreateCustomUnit('sunfire', function(unit) + local sunfireTarget = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and + ( + not unit:GetAuras():FindMy(SunfireAura):IsUp() or + unit:GetAuras():FindMy(SunfireAura):GetRemainingTime() <= 3.6) then + sunfireTarget = unit + end + end) + + if sunfireTarget == nil then + sunfireTarget = None + end + + return sunfireTarget + end) + +local RestoCommands = Bastion.Command:New('resto') + +local PLACE_EFFLO = false + +RestoCommands:Register('efflo', 'Request the engine to place an Efflorescence', function() + PLACE_EFFLO = true + Bastion.Notifications:AddNotification(Efflorescence:GetIcon(), "Efflorescence requested") +end) + +local DefaultAPL = Bastion.APL:New('default') +local DamageAPL = Bastion.APL:New('damage') + +DamageAPL:AddSpell( + Rake:CastableIf(function(self) + return RakeTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + ( + not RakeTarget:GetAuras():FindMy(RakeDebuff):IsUp() or + RakeTarget:GetAuras():FindMy(RakeDebuff):GetRemainingTime() <= 3.6) + end):SetTarget(RakeTarget) +) + +DamageAPL:AddSpell( + FerociousBite:CastableIf(function(self) + return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + Player:GetComboPoints() >= 5 + end):SetTarget(Target) +) + +DamageAPL:AddSpell( + Shred:CastableIf(function(self) + return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + Player:GetComboPoints() < 5 + end):SetTarget(Target) +) + + +DefaultAPL:AddSpell( + Efflorescence:CastableIf(function(self) + return PLACE_EFFLO and Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + end):SetTarget(None):OnCast(function(self) + local loc = Bastion.UnitManager:FindFriendsCentroid(10, 40) + PLACE_EFFLO = false + self:Click(loc) + end) +) + +CatForm:OnCast(function(self) + if not Player:GetAuras():FindMy(Prowl):IsUp() and not Player:IsAffectingCombat() then + Prowl:Cast(Player) + end +end) + +DefaultAPL:AddAction( + 'cat_form_shift', + function() + if (IsShiftKeyDown()) and not Player:IsMounted() and + not Player:GetAuras():FindMy(CatForm):IsUp() and + not Player:IsCastingOrChanneling() then + CatForm:Cast(Player) + elseif (not IsShiftKeyDown() and Player:IsAffectingCombat()) and Player:GetAuras():FindMy(CatForm):IsUp() then + CancelShapeshiftForm() + end + end +) + +DefaultAPL:AddSpell( + NaturesCure:CastableIf(function(self) + return DispelTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(DispelTarget) and DispelTarget:GetAuras():HasAnyDispelableAura(NaturesCure) + end):SetTarget(DispelTarget) +) + +DefaultAPL:AddSpell( + Soothe:CastableIf(function(self) + return PurgeTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(PurgeTarget) and PurgeTarget:GetAuras():HasAnyStealableAura() + end):SetTarget(PurgeTarget) +) + +DefaultAPL:AddSpell( + NaturesSwiftness:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and Lowest:GetHP() < 70 + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + ConvokeTheSpirits:CastableIf(function(self) + return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(Player) and (Player:GetPartyHPAround(40, 70) >= 2 or Player:GetPartyHPAround(40, 75) >= 3) + and (Flourish:IsKnownAndUsable() or Flourish:GetTimeSinceLastCast() > 10) + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + Flourish:CastableIf(function(self) + return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(Player) and (Player:GetPartyHPAround(40, 70) >= 2 or Player:GetPartyHPAround(40, 75) >= 3) and + (not ConvokeTheSpirits:IsKnownAndUsable() and ConvokeTheSpirits:GetTimeSinceLastCast() > 10) and + WildGrowth:GetTimeSinceLastCast() <= 6 + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + NaturesVigil:CastableIf(function(self) + return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(Player) and Flourish:GetTimeSinceLastCast() <= 5 + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + AdaptiveSwarm:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + Swiftmend:CastableIf(function(self) + return SwiftmendUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(SwiftmendUnit) and + ( + SwiftmendUnit:GetHP() <= 60 or + ( + Lowest:GetPartyHPAround(30, 90) >= 3 or Lowest:GetPartyHPAround(30, 85) >= 2 + ) + ) + end):SetTarget(SwiftmendUnit) +) + +DefaultAPL:AddSpell( + WildGrowth:CastableIf(function(self) + return WildGrowthUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(WildGrowthUnit) and + ( + Player:GetAuras():FindMy(SoulOfTheForest):IsUp() + or + (WildGrowthUnit:GetPartyHPAround(30, 90) >= 3 or WildGrowthUnit:GetPartyHPAround(30, 85) >= 2) + ) and + not Player:IsMoving() + end):SetTarget(WildGrowthUnit) +) + +DefaultAPL:AddSpell( + Regrowth:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and Lowest:GetHP() < 65 and + Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and not Player:IsMoving() + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + Regrowth:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and Lowest:GetHP() < 70 and + ( + NaturesSwiftness:GetTimeSinceLastCast() < 2 or Player:GetAuras():FindMy(NaturesSwiftness):IsUp() or + NaturesSwiftness:IsKnownAndUsable()) and not Player:IsMoving() and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + CenarionWard:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and Lowest:GetHP() <= 90 + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + Ironbark:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and Lowest:GetHP() <= 70 and not Lowest:GetAuras():FindMy(CenarionWard):IsUp() + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + Rejuvenation:CastableIf(function(self) + return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(RejuvUnit) and (RejuvUnit:GetHP() <= 94) and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() + end):SetTarget(RejuvUnit) +) + +DefaultAPL:AddSpell( + Lifebloom:CastableIf(function(self) + return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and + ( + not Player:GetAuras():FindMy(LifebloomAura):IsUp() or + Player:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Player:IsAffectingCombat() + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + Lifebloom:CastableIf(function(self) + return Tank:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and + ( + not Tank:GetAuras():FindMy(LifebloomAura):IsUp() or + Tank:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Tank:IsAffectingCombat() + end):SetTarget(Tank) +) + +DefaultAPL:AddSpell( + Regrowth:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and + ( + not Player:GetAuras():FindMy(Regrowth):IsUp() and Lowest:GetHP() < 70 or + (Lowest:GetHP() <= 85 and Player:GetAuras():FindMy(ClearCasting):IsUp())) and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and + not Player:IsMoving() + end):SetTarget(Lowest) +) + +DefaultAPL:AddSpell( + Rejuvenation:CastableIf(function(self) + return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(RejuvUnit) and (RejuvUnit:GetHP() <= 94 or Player:GetPartyHPAround(40, 90) >= 2) and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() + end):SetTarget(RejuvUnit) +) + + +DefaultAPL:AddSpell( + Moonfire:CastableIf(function(self) + return Explosive:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + end):SetTarget(Explosive) +) + +DefaultAPL:AddSpell( + Sunfire:CastableIf(function(self) + return SunfireTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(SunfireTarget) and + ( + not SunfireTarget:GetAuras():FindMy(SunfireAura):IsUp() or + SunfireTarget:GetAuras():FindMy(SunfireAura):GetRemainingTime() <= 5.4) and + SunfireTarget:IsHostile() and + SunfireTarget:IsAffectingCombat() and Player:GetPP() >= 25 + end):SetTarget(SunfireTarget) +) + +DefaultAPL:AddSpell( + Moonfire:CastableIf(function(self) + return MoonfireTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(MoonfireTarget) and + ( + not MoonfireTarget:GetAuras():FindMy(MoonfireAura):IsUp() or + MoonfireTarget:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 5.4) and + MoonfireTarget:IsHostile() and + MoonfireTarget:IsAffectingCombat() and Player:GetPP() >= 25 + end):SetTarget(MoonfireTarget) +) + +DefaultAPL:AddSpell( + Starsurge:CastableIf(function(self) + return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Target) and Target:IsHostile() and + Target:IsAffectingCombat() and Player:GetPP() >= 25 + end):SetTarget(Target) +) + +DefaultAPL:AddSpell( + Wrath:CastableIf(function(self) + return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Target) and not Player:IsMoving() and + Target:IsHostile() and + Target:IsAffectingCombat() and Player:GetPP() >= 25 + end):SetTarget(Target) +) + +RestoModule:Sync(function() + if IsShiftKeyDown() and Player:GetAuras():FindMy(CatForm):IsUp() then + return DamageAPL:Execute() + end + DefaultAPL:Execute() +end) + +Bastion:Register(RestoModule) diff --git a/subtlety.lua b/subtlety.lua new file mode 100644 index 0000000..d1432f6 --- /dev/null +++ b/subtlety.lua @@ -0,0 +1,1379 @@ +local Tinkr, Bastion = ... + +local SubModulue = Bastion.Module:New('sub') +local Evaluator = Tinkr.Util.Evaluator +local Player = Bastion.UnitManager:Get('player') +local None = Bastion.UnitManager:Get('none') +local Target = Bastion.UnitManager:Get('target') + +Player:WatchForSwings() + +local RollTheBones = Bastion.SpellBook:GetSpell(315508) +local SliceAndDice = Bastion.SpellBook:GetSpell(315496) +local BetweenTheEyes = Bastion.SpellBook:GetSpell(315341) +local BladeRush = Bastion.SpellBook:GetSpell(271877) +local Vanish = Bastion.SpellBook:GetSpell(1856) +local Dispatch = Bastion.SpellBook:GetSpell(2098) +local Ambush = Bastion.SpellBook:GetSpell(8676) +local FlagellationPersist = Bastion.SpellBook:GetSpell(345569) +local Stealth = Bastion.SpellBook:GetSpell(1784) +local PistolShot = Bastion.SpellBook:GetSpell(185763) +local Opportunity = Bastion.SpellBook:GetSpell(195627) +local SinisterStrike = Bastion.SpellBook:GetSpell(193315) +local Blind = Bastion.SpellBook:GetSpell(2094) +local GrandMelee = Bastion.SpellBook:GetSpell(193358) +local Broadside = Bastion.SpellBook:GetSpell(193356) +local TrueBearing = Bastion.SpellBook:GetSpell(193359) +local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) +local Gouge = Bastion.SpellBook:GetSpell(1776) +local DeviousStratagem = Bastion.SpellBook:GetSpell(193531) +local SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) +local ShadowFocus = Bastion.SpellBook:GetSpell(108209) +local BuriedTreasure = Bastion.SpellBook:GetSpell(199600) +local AdrenalineRush = Bastion.SpellBook:GetSpell(13750) +local ShadowDance = Bastion.SpellBook:GetSpell(185313) +local ShadowDanceAura = Bastion.SpellBook:GetSpell(185422) +local Shadowmeld = Bastion.SpellBook:GetSpell(58984) +local Audacity = Bastion.SpellBook:GetSpell(381845) +local SealFate = Bastion.SpellBook:GetSpell(14190) +local GlobalCooldown = Bastion.SpellBook:GetSpell(61304) +local Flagellation = Bastion.SpellBook:GetSpell(323654) +local Dreadblades = Bastion.SpellBook:GetSpell(343142) +local MasterOfShadows = Bastion.SpellBook:GetSpell(196976) +local JollyRoger = Bastion.SpellBook:GetSpell(199603) +local BladeFlurry = Bastion.SpellBook:GetSpell(13877) +local Kick = Bastion.SpellBook:GetSpell(1766) +local MarkedForDeath = Bastion.SpellBook:GetSpell(137619) +local CrimsonVial = Bastion.SpellBook:GetSpell(185311) +local TheRotten = Bastion.SpellBook:GetSpell(394203) +local Shiv = Bastion.SpellBook:GetSpell(5938) +local KidneyShot = Bastion.SpellBook:GetSpell(408) +local InstantPoison = Bastion.SpellBook:GetSpell(315584) +local Sanguine = Bastion.SpellBook:GetSpell(226512) +local AtrophicPosion = Bastion.SpellBook:GetSpell(381637) +local Evasion = Bastion.SpellBook:GetSpell(5277) +local TricksOfTheTrade = Bastion.SpellBook:GetSpell(57934) +local Backstab = Bastion.SpellBook:GetSpell(53) +local CheapShot = Bastion.SpellBook:GetSpell(1833) +local BagOfTricks = Bastion.SpellBook:GetSpell(312411) +local Alacrity = Bastion.SpellBook:GetSpell(193539) +local PerforatedVeins = Bastion.SpellBook:GetSpell(394254) +local AutoAttack = Bastion.SpellBook:GetSpell(6603) +local DeeperStratagem = Bastion.SpellBook:GetSpell(193531) +local SecretStratagem = Bastion.SpellBook:GetSpell(394320) +local SymbolsOfDeath = Bastion.SpellBook:GetSpell(212283) +local ShadowBlades = Bastion.SpellBook:GetSpell(121471) +local Vigor = Bastion.SpellBook:GetSpell(14983) +local ColdBlood = Bastion.SpellBook:GetSpell(382245) +local ShurikenTornado = Bastion.SpellBook:GetSpell(277925) +local ThistleTea = Bastion.SpellBook:GetSpell(381623) +local Gloomblade = Bastion.SpellBook:GetSpell(200758) +local Shadowstrike = Bastion.SpellBook:GetSpell(185438) +local Rupture = Bastion.SpellBook:GetSpell(1943) +local Eviscerate = Bastion.SpellBook:GetSpell(196819) +local ResoundingClarity = Bastion.SpellBook:GetSpell(381622) +local ArcanePulse = Bastion.SpellBook:GetSpell(260364) +local NumbingPoison = Bastion.SpellBook:GetSpell(5761) +local ShurikenStorm = Bastion.SpellBook:GetSpell(197835) +local BlackPowder = Bastion.SpellBook:GetSpell(319175) +local Sepsis = Bastion.SpellBook:GetSpell(385408) +local SecretTechnique = Bastion.SpellBook:GetSpell(280719) +local DarkBrew = Bastion.SpellBook:GetSpell(310454) +local Premeditation = Bastion.SpellBook:GetSpell(343173) +local ArcaneTorrent = Bastion.SpellBook:GetSpell(50613) +local DanseMacabre = Bastion.SpellBook:GetSpell(393969) +local LingeringShadow = Bastion.SpellBook:GetSpell(385960) +local EchoingReprimand = Bastion.SpellBook:GetSpell(385616) +local LightsJudgment = Bastion.SpellBook:GetSpell(255647) +local Subterfuge = Bastion.SpellBook:GetSpell(108208) +local EchoingReprimand2 = Bastion.SpellBook:GetSpell(323558) +local EchoingReprimand3 = Bastion.SpellBook:GetSpell(323559) +local EchoingReprimand4 = Bastion.SpellBook:GetSpell(323560) +local EchoingReprimand5 = Bastion.SpellBook:GetSpell(354838) +local SilentStorm = Bastion.SpellBook:GetSpell(385727) +local FindWeakness = Bastion.SpellBook:GetSpell(91023) +local ImprovedShurikenStorm = Bastion.SpellBook:GetSpell(319951) +local Feint = Bastion.SpellBook:GetSpell(1966) +local FinalityRupture = Bastion.SpellBook:GetSpell(385951) + +local RefreshingHealingPotion = Bastion.ItemBook:GetItem(191380) +local ElementalPotionOfPower = Bastion.ItemBook:GetItem(191389) +local IrideusFragment = Bastion.ItemBook:GetItem(193743) +local Healthstone = Bastion.ItemBook:GetItem(5512) +local WindscarWhetstone = Bastion.ItemBook:GetItem(137486) +local AlgetharsPuzzleBox = Bastion.ItemBook:GetItem(193701) + +local DeadgeHealth = 120000 * 12 + +local function IsDeadge(unit) return false end + +local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) + local purge = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if unit:GetAuras():HasAnyStealableAura() and Shiv:IsInRange(unit) then + purge = unit + return true + end + end) + + if purge == nil then + purge = None + end + + return purge + end) + +local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) + local kick = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalKick(unit, 5) then + kick = unit + return true + end + end) + + if kick == nil then + kick = None + end + + return kick + end) + +local StunTarget = Bastion.UnitManager:CreateCustomUnit('stun', function(unit) + local stun = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalStun(unit, 5) then + stun = unit + return true + end + end) + + if stun == nil then + stun = None + end + + return stun + end) + +local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) + local tank = nil + + Bastion.UnitManager:EnumFriends(function(unit) + if Player:GetDistance(unit) > 40 then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if unit:IsDead() then + return false + end + + if unit:IsTank() then + tank = unit + return true + end + + return false + end) + + if tank == nil then + tank = None + end + + return tank + end) + +local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function() + local target = nil + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if not Player:InMelee(unit) then + return false + end + + if not Player:IsFacing(unit) then + return false + end + + if ( + not unit:GetAuras():FindMy(Rupture):IsUp() or + unit:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 + ) + and unit:GetHealth() > DeadgeHealth + then + target = unit + return true + end + end) + + if target == nil then + target = None + end + + return target + end) + + + +local DefaultAPL = Bastion.APL:New('default') +local ItemsAPL = Bastion.APL:New('items') +local DefensivesAPL = Bastion.APL:New('defensives') +local InterruptAPL = Bastion.APL:New('interrupt') +local BuildersAPL = Bastion.APL:New('builders') +local FinishersAPL = Bastion.APL:New('finishers') +local CDsAPL = Bastion.APL:New('cds') +local OpenersAPL = Bastion.APL:New('openers') +local TrinketsAPL = Bastion.APL:New('trinkets') + +function WasSelfHealUsedWithin(time) + local timeSinceHealthstone = Healthstone:GetTimeSinceLastUseAttempt() + local timeSincePotion = RefreshingHealingPotion:GetTimeSinceLastUseAttempt() + + return timeSinceHealthstone < time or timeSincePotion < time +end + +function WasInterruptUsedWithin(time) + local timeSinceKick = Kick:GetTimeSinceLastCastAttempt() + local timeSinceKidney = KidneyShot:GetTimeSinceLastCastAttempt() + local timeSinceGouge = Gouge:GetTimeSinceLastCastAttempt() + local timeSinceBlind = Blind:GetTimeSinceLastCastAttempt() + + return timeSinceKick < time or timeSinceKidney < time or timeSinceGouge < time or timeSinceBlind < time +end + +local FeintOn = { + [397878] = true, + [152964] = true, + [153094] = true, + [191284] = true, + [200901] = true, + [196512] = true, + [198058] = true, + [212784] = true, + [214692] = true, + [397892] = true, + [209741] = true, + [377004] = true, + [388923] = true, + [388537] = true, + [396991] = true, + [3747312] = true, + [384132] = true, + [388804] = true, + [388817] = true, + [384620] = true, + [375943] = true, + [376894] = true, + [372735] = true, + [373692] = true, + [392486] = true, + [392641] = true, + [384823] = true, + [381516] = true, +} + +local lastFeintTime = 0 + +Bastion.EventManager:RegisterWoWEvent('COMBAT_LOG_EVENT_UNFILTERED', function() + -- Check if the spell cast/channel is started is in a list + local _, event, _, sourceGUID, _, _, _, destGUID, _, _, _, spellID = CombatLogGetCurrentEventInfo() + + if event == 'SPELL_CAST_START' and FeintOn[spellID] then + lastFeintTime = GetTime() + end +end) + +-- We should feint if we are about to take aoe damage +DefensivesAPL:AddSpell( + Feint:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + GetTime() - lastFeintTime < 4 + end):SetTarget(Player) +) + +DefensivesAPL:AddSpell( + CrimsonVial:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 70 and + not WasSelfHealUsedWithin(5) + end):SetTarget(Player) +) + +DefensivesAPL:AddItem( + Healthstone:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 40 and + not WasSelfHealUsedWithin(5) + end):SetTarget(Player) +) + +DefensivesAPL:AddItem( + RefreshingHealingPotion:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 40 and + not WasSelfHealUsedWithin(5) + end):SetTarget(Player) +) + +DefensivesAPL:AddSpell( + TricksOfTheTrade:CastableIf(function(self) + return Tank:Exists() and Target:Exists() and self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Player:IsTanking(Target) + end):SetTarget(Tank) +) + +-- DefensivesAPL:AddSpell( +-- Evasion:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetHealthPercent() < 40 +-- end):SetTarget(Player) +-- ) + +TrinketsAPL:AddItem( + IrideusFragment:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) +) + +TrinketsAPL:AddItem( + ElementalPotionOfPower:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) +) + +TrinketsAPL:AddItem( + WindscarWhetstone:UsableIf(function(self) + return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and Target:GetHealth() and (Player:GetEnemies(10) > 2 or Target:IsBoss()) + end):SetTarget(Player) +) + +TrinketsAPL:AddItem( + AlgetharsPuzzleBox:UsableIf(function(self) + return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 3 or Target:IsBoss()) and + not Player:IsMoving() + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + Stealth:CastableIf( + function(self) + return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and + not Player:IsAffectingCombat() and not IsMounted() + end + ):SetTarget(Player) +) + +DefaultAPL:AddSpell( + Kick:CastableIf(function(self) + return KickTarget:Exists() and self:IsInRange(KickTarget) and + self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) and not WasInterruptUsedWithin(2) and + not IsDeadge(Target) + end):SetTarget(KickTarget) +) + +DefaultAPL:AddSpell( + Gouge:CastableIf(function(self) + return StunTarget:Exists() and self:IsInRange(StunTarget) and + self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) and + (Player:GetDistance(Target) < 1.5 or not Player:IsBehind(Target)) + and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and not WasInterruptUsedWithin(2) and + not IsDeadge(Target) + end):SetTarget(StunTarget) +) + +DefaultAPL:AddSpell( + KidneyShot:CastableIf(function(self) + return StunTarget:Exists() and self:IsInRange(StunTarget) and + self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) + and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and not WasInterruptUsedWithin(2) and + not IsDeadge(Target) + end):SetTarget(StunTarget) +) + +DefaultAPL:AddSpell( + Blind:CastableIf(function(self) + return StunTarget:Exists() and self:IsInRange(StunTarget) and + self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) + and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and not WasInterruptUsedWithin(2) and + not IsDeadge(Target) + end):SetTarget(StunTarget) +) + +-- DefaultAPL:AddSpell( +-- CheapShot:CastableIf(function(self) +-- return StunTarget:Exists() and self:IsInRange(StunTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:IsFacing(Target) and not Player:IsBehind(Target) and +-- Player:IsStealthed() and Player:GetPower() > 80 -- Cheap shot costs a lot so lets only use it with a surplus +-- and Player:GetComboPoints() < 3 and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and +-- not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(StunTarget) +-- ) + +-- Purge +DefaultAPL:AddSpell( + Shiv:CastableIf(function(self) + return PurgeTarget:Exists() and + self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) and not IsDeadge(Target) and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() + end):SetTarget(PurgeTarget) +) + +--[[ + Subtlety Rogue Opener Sequence (Single Target) + + 1. Ensure you are in Stealth. + 2. Use Shadowstrike. + 3. Use Symbols of Death. | With Symbols of Death, use your offensive potion. + 4. Use Shadow Blades. + 5. Use Gloomblade. + 6. Use Rupture. + 7. Use Shadow Dance. + 7.5 Use Thistle Tea + 8. Use Gloomblade. | Use shadow strike if we don't have `The First Dance` + 9. Use Eviscerate + 10. Use Shadowstrike + 11. Use Secret Technique | With Secret Technique use Cold Blood + 12. Vanish + 13. Use Shadowstrike + + -- Continue with normal rotation +]] +local function CanPerformSTOpener() + return ShadowDance:IsKnownAndUsable() and + ShadowBlades:IsKnownAndUsable() and + SymbolsOfDeath:IsKnownAndUsable() and + Vanish:GetCharges() > 0 and + ThistleTea:GetCharges() > 0 and + ColdBlood:IsKnownAndUsable() +end + +local function ShouldPerformSTOpener() + -- If the target will survive the opener we should perform it + return Target:TimeToDie() > + Player:GetMaxGCD() * 13 + 5 and Player:GetEnemies(10) <= 2 +end + +local function ShouldResetSTOpener() + -- If we are no longer in combat and we can perform the opener we should reset it so we can perform it again in the future + return not Player:IsAffectingCombat() and CanPerformSTOpener() +end + +local function OpenerAbortCondition(spell) + -- If the cooldown is over 2 seconds we should abort the opener + return spell:GetCooldownRemaining() > 2 +end + +local OpenerSequenceST = Bastion.Sequencer:New({ + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end + + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(SymbolsOfDeath) then + self:Abort() + print("Aborting on SymbolsOfDeath") + return true + end + + if SymbolsOfDeath:IsKnownAndUsable() then + SymbolsOfDeath:ForceCast(Player) + ElementalPotionOfPower:Use(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShadowBlades) then + self:Abort() + print("Aborting on ShadowBlades") + return true + end + + if ShadowBlades:IsKnownAndUsable() then + ShadowBlades:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Gloomblade) then + self:Abort() + print("Aborting on Gloomblade") + return true + end + + if Gloomblade:IsKnownAndUsable() then + Gloomblade:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Rupture) then + self:Abort() + print("Aborting on Rupture") + return true + end + + if Rupture:IsKnownAndUsable() then + Rupture:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShadowDance) then + self:Abort() + print("Aborting on ShadowDance", ShadowDance:GetCooldownRemaining()) + return true + end + + if ShadowDance:IsKnownAndUsable() then + ShadowDance:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ThistleTea) then + self:Abort() + print("Aborting on ThistleTea") + return true + end + + if ThistleTea:IsKnownAndUsable() then + ThistleTea:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Gloomblade) then + self:Abort() + print("Aborting on Gloomblade") + return true + end + + if Gloomblade:IsKnownAndUsable() then + Gloomblade:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Eviscerate) then + self:Abort() + print("Aborting on Eviscerate") + return true + end + + if Eviscerate:IsKnownAndUsable() then + Eviscerate:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end + + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(SecretTechnique) then + self:Abort() + print("Aborting on SecretTechnique") + print("Aborting on ColdBlood") + return true + end + + if SecretTechnique:IsKnownAndUsable() then + SecretTechnique:ForceCast(Target) + ColdBlood:ForceCast(Target) + return true + end + return false + end, + function(self) + -- Somehow we lost our vanish charges, abort the opener + if Vanish:GetCharges() < 1 then + self:Abort() + print("Aborting on Vanish") + return true + end + + if Vanish:IsKnownAndUsable() then + Vanish:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end + + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + }, ShouldResetSTOpener) + +--[[ + Subtlety Opener Sequence for AoE + + 1. Ensure you are in Stealth. + 2. Shadowstrike + 3. Symbols of Death + 4. Shadow Blades + 5. Shuriken Tornado + 6. Shadow Dance | Use our offensive potion here + 7. Thistle Tea + 8. Rupture + 9. Black Powder + 10. Cold Blood and Secret Technique + 11. Black Powder + 12. Shadowstrike + 13. Black Powder + 14. Shuriken Storm + + ss, sd, sb, st, sd, tt, ru, bp, stcb, bp, ss, bp, sstor + + -- Continue with the normal rotation +]] +local function CanPerformAOEOpener() + return ShadowDance:IsKnownAndUsable() and + ShadowBlades:IsKnownAndUsable() and + SymbolsOfDeath:IsKnownAndUsable() and + ThistleTea:GetCharges() > 0 and + ColdBlood:IsKnownAndUsable() and ShurikenTornado:IsKnownAndUsable() +end + +local function ShouldPerformAOEOpener() + -- If the target will survive the opener we should perform it + return Target:TimeToDie() > + Player:GetMaxGCD() * 13 + 5 and Player:GetEnemies(10) > 2 +end + +local function ShouldResetAOEOpener() + -- If we are no longer in combat and we can perform the opener we should reset it so we can perform it again in the future + return not Player:IsAffectingCombat() and CanPerformAOEOpener() +end + +local OpenerSequenceAOE = Bastion.Sequencer:New({ + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end + + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(SymbolsOfDeath) then + self:Abort() + print("Aborting on SymbolsOfDeath") + return true + end + + if SymbolsOfDeath:IsKnownAndUsable() then + SymbolsOfDeath:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShadowBlades) then + self:Abort() + print("Aborting on ShadowBlades") + return true + end + + if ShadowBlades:IsKnownAndUsable() then + ShadowBlades:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShurikenTornado) then + self:Abort() + print("Aborting on ShurikenTornado") + return true + end + + if ShurikenTornado:IsKnownAndUsable() then + ShurikenTornado:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShadowDance) then + self:Abort() + print("Aborting on ShadowDance") + return true + end + + if ShadowDance:IsKnownAndUsable() then + ShadowDance:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ThistleTea) then + self:Abort() + print("Aborting on ThistleTea") + return true + end + + if ThistleTea:IsKnownAndUsable() then + ThistleTea:ForceCast(Player) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Rupture) then + self:Abort() + print("Aborting on Rupture") + return true + end + + if Rupture:IsKnownAndUsable() then + Rupture:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + end + + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(SecretTechnique) then + self:Abort() + print("Aborting on SecretTechnique") + print("Aborting on ColdBlood") + return true + end + + if SecretTechnique:IsKnownAndUsable() then + SecretTechnique:ForceCast(Target) + ColdBlood:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + end + + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end + + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + end + + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, + function(self) + if OpenerAbortCondition(ShurikenStorm) then + self:Abort() + print("Aborting on ShurikenStorm") + return true + end + + if ShurikenStorm:IsKnownAndUsable() then + ShurikenStorm:ForceCast(Target) + return true + end + return false + end, + }, ShouldResetAOEOpener) + +--[[ + == Subtlety Rogue Concepts == + + Keep Rupture and Slice and Dice up and stack cooldowns if reasonable. + Combo point Generators(Builders) are used when on low combo point value + Finishing moves are used to consume combo points when reaching six or more (five or more duringShadow Dance). +]] +-- Builders + +-- Should we pool for a Shadow Dance or Symbols of Death? +local function ShouldPoolForCD() + local SD = ShadowDance:GetCooldownRemaining() + local power = Player:GetPower() + local cp = Player:GetComboPoints() + local es = Player:GetEnemies(10) + + if es > 3 then return false end + + if power > 80 then return false end + + if SD < 3 and power < 80 then + return true + end + + return false +end + +-- Gloomblade/Backstab: outside of Shadow Dance. +BuildersAPL:AddSpell( + Gloomblade:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetEnemies(10) <= 1 or (Player:GetEnemies(10) <= 2 and Player:GetAuras():FindMy(LingeringShadow):GetRemainingTime() > 6)) and + self:IsInRange(Target) + and not ShouldPoolForCD() + end):SetTarget(Target) +) + +-- BuildersAPL:AddSpell( +-- Backstab:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Target) +-- ) + +-- Shadowstrike: during Shadow Dance. +BuildersAPL:AddSpell( + Shadowstrike:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetEnemies(10) <= 2 or (Player:GetEnemies(10) <= 2 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) and + self:IsInRange(Target) and Player:GetEnemies(10) < 6 and + not ShouldPoolForCD() + end):SetTarget(Target) +) + +-- Shuriken Storm: on 2 or more targets (3 or more during Shadow Dance). +BuildersAPL:AddSpell( + ShurikenStorm:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + ((Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and Player:GetEnemies(10) >= 3) or + (not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and Player:GetEnemies(10) >= 2)) and + self:IsInRange(Target) and + not ShouldPoolForCD() + end):SetTarget(Target) +) + +--[[ + Optimizing around finality is mainly about consuming the buff in time, the optimal way to do so is to + prepare Rupture for Shadow Dance. This means to use Rupture if the remaining cooldown of Shadow Dance is + lower than 10 seconds and you have the Finality: Rupture buff. + +]] +-- Rupture: any target that survives at least 12 seconds. +FinishersAPL:AddSpell( + Rupture:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Target:Exists() and + (Target:GetAuras():FindMy(Rupture):GetRemainingTime() < 8.4 or ((Player:GetAuras():FindMy(FinalityRupture):IsUp() and Player:GetAuras():FindMy(FinalityRupture):GetRemainingTime() < 10) and ShadowDance:GetCooldownRemaining() < 10) or (ShadowDance:GetCooldownRemaining() < 3 and (Target:GetAuras():FindMy(Rupture):GetRemainingTime() - 8 < 4))) and + self:IsInRange(Target) and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + not IsDeadge(Target) and not Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + not ShadowDance:IsKnownAndUsable() and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + Target:GetHealth() > DeadgeHealth + end):SetTarget(Target) +) + +-- Slice and Dice: 1 - 5 targets. +FinishersAPL:AddSpell( + SliceAndDice:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + (Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 12 or (ShadowDance:GetCooldownRemaining() < 3 and (Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() - 12 < 4))) and + Player:GetEnemies(10) <= 5 and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and not Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + not ShadowDance:IsKnownAndUsable() and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() + end):SetTarget(Player) +) + +FinishersAPL:AddSpell( + Rupture:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + RuptureTarget:Exists() and + (RuptureTarget:GetAuras():FindMy(Rupture):GetRemainingTime() < 8.4 or ((Player:GetAuras():FindMy(FinalityRupture):IsUp() and Player:GetAuras():FindMy(FinalityRupture):GetRemainingTime() < 10) and ShadowDance:GetCooldownRemaining() < 10) or (ShadowDance:GetCooldownRemaining() < 3 and (RuptureTarget:GetAuras():FindMy(Rupture):GetRemainingTime() - 8 < 4))) and + self:IsInRange(RuptureTarget) and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + not IsDeadge(RuptureTarget) and not Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + not ShadowDance:IsKnownAndUsable() and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + RuptureTarget:GetHealth() > DeadgeHealth + end):SetTarget(RuptureTarget) +) + +-- Secret Technique: During Shadow Dance, as 2nd finisher with Danse Macabre. +-- FinishersAPL:AddSpell( +-- SecretTechnique:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and self:IsInRange(Target) +-- end):SetTarget(Target) +-- ) + +-- Black Powder: 3 or more targets. +FinishersAPL:AddSpell( + BlackPowder:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetEnemies(10) >= 3 and self:IsInRange(Target) and not ShadowDance:IsKnownAndUsable() + end):SetTarget(Target) +) + +-- Eviscerate: 1 and 2 targets or for priority damage. +FinishersAPL:AddSpell( + Eviscerate:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + (Player:GetEnemies(10) <= 2) and self:IsInRange(Target) and not ShadowDance:IsKnownAndUsable() + end):SetTarget(Target) +) + + +-- Symbols of Death: on cooldown. +CDsAPL:AddSpell( + SymbolsOfDeath:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and not IsDeadge(Target) and + GlobalCooldown:GetCooldownRemaining() <= 0 + end):SetTarget(Player):OnCast(function() + ShurikenTornado:ForceCast(Player) + end) +) + +CDsAPL:AddSpell( + ShurikenTornado:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + (Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() or SymbolsOfDeath:GetCooldownRemaining() > 10) and + Player:GetAuras():FindMy(SymbolsOfDeath):GetRemainingTime() > 3.5 and not IsDeadge(Target) and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() + end):SetTarget(Player) +) + +-- Shadow Dance: on cooldown +CDsAPL:AddSpell( + ShadowDance:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetPower() >= 80 or Player:GetEnemies(10) > 2) and + not IsDeadge(Target) and + SymbolsOfDeath:GetCooldownRemaining() > 2 and GlobalCooldown:GetCooldownRemaining() <= 0 + end):SetTarget(Player):OnCast(function() + if Player:GetEnemies(10) > 3 then + ThistleTea:ForceCast(Player) + end + if Player:GetAuras():FindMy(SilentStorm):IsUp() then + ShurikenStorm:ForceCast(Target) + else + Gloomblade:ForceCast(Target) + end + end) +) + +-- Shadow Blades: on cooldown. +CDsAPL:AddSpell( + ShadowBlades:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and not IsDeadge(Target) + end):SetTarget(Player) +) + +-- Vanish: After Secret Technique during Shadow Dance +CDsAPL:AddSpell( + Vanish:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + SecretTechnique:GetTimeSinceLastCast() <= 3 and Vanish:GetCharges() > 1 and Player:GetEnemies(10) < 7 and + GlobalCooldown:GetCooldownRemaining() <= 0 + end):SetTarget(Player):OnCast(function() + Shadowstrike:ForceCast(Target) + end) +) + +local function HasRampedInDance() + if ShadowDance:GetTimeSinceLastCast() > 8 then + return false + end + + local ramped = false + local lastSD = ShadowDance:GetTimeSinceLastCast() + local lastEviscerate = Eviscerate:GetTimeSinceLastCast() + local lastBP = BlackPowder:GetTimeSinceLastCast() + local lastST = SecretTechnique:GetTimeSinceLastCast() + + -- if bp or ev happened before sd we are not ramped + return lastBP < lastSD or lastEviscerate < lastSD +end + +-- Consider this a cooldown since it's tied to shadow dance ig and it has to be happening before cb/st. +CDsAPL:AddSpell( + Eviscerate:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + self:IsInRange(Target) and Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + not HasRampedInDance() -- not sure yet + and Player:GetComboPoints() >= 5 and Player:GetEnemies(10) < 3 + end):SetTarget(Target) +) + +FinishersAPL:AddSpell( + BlackPowder:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + self:IsInRange(Target) and Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + not HasRampedInDance() -- not sure yet + and Player:GetComboPoints() >= 5 and Player:GetEnemies(10) >= 3 + end):SetTarget(Target) +) + +-- Cold Blood: before using a finishing move / before Secret Technique (if talented). +CDsAPL:AddSpell( + ColdBlood:CastableIf(function(self) + return (self:IsKnownAndUsable() or (SecretTechnique:IsKnownAndUsable() and ColdBlood:GetCooldownRemaining() > 2)) and + not Player:IsCastingOrChanneling() and SecretTechnique:IsKnownAndUsable() and + SecretTechnique:IsInRange(Target) and Player:GetAuras():FindMy(ShadowDanceAura):IsUp() + and Player:GetComboPoints() >= 5 and not IsDeadge(Target) and HasRampedInDance() + end):SetTarget(Player):OnCast(function() + SecretTechnique:ForceCast(Target) + end) +) + +-- Thistle Tea: when on low energy (Single Target) / with Shadow Dance (Multi Target or on max charges). +CDsAPL:AddSpell( + ThistleTea:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + ((Player:GetEnemies(10) <= 1 and Player:GetPower() <= 20)) and + not IsDeadge(Target) and not Player:GetAuras():FindMy(ThistleTea):IsUp() + end):SetTarget(Player) +) + +-- DefaultAPL:AddSequence( +-- OpenerSequenceST, +-- ShouldPerformSTOpener +-- ) + +-- DefaultAPL:AddSequence( +-- OpenerSequenceAOE, +-- ShouldPerformAOEOpener +-- ) + +-- DefaultAPL:AddAPL( +-- OpenersAPL, +-- function() +-- return ShouldPerformSTOpener() or ShouldPerformAOEOpener() +-- end +-- ) + +DefaultAPL:AddSpell( + Shadowstrike:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + self:IsInRange(Target) and Player:GetAuras():FindMy(Premeditation):IsUp() and Player:GetEnemies(10) <= 6 + end):SetTarget(Target) +) + +DefaultAPL:AddAPL( + DefensivesAPL, + function() + return true + end +) + +DefaultAPL:AddAPL( + ItemsAPL, + function() + return true + end +) + +DefaultAPL:AddAPL( + TrinketsAPL, + function() + return Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and not IsDeadge(Target) + end +) + +DefaultAPL:AddAPL( + CDsAPL, + function() -- trying out a way to hold CDs, maybe it's dog + return Player:IsAffectingCombat() and not IsShiftKeyDown() + end +) + +-- Use Finishing moves with 6 or more combo points (5 or more during Shadow Dance) with the following priority: +DefaultAPL:AddAPL( + FinishersAPL, + function() + return Player:GetComboPoints() >= 6 or + (Player:GetComboPoints() >= 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) or + (Player:GetEnemies(10) > 3 and Player:GetComboPoints() >= 4) + end +) + +DefaultAPL:AddSpell( + BagOfTricks:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + self:IsInRange(Target) and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and Player:GetEnemies(10) <= 2 + end):SetTarget(Target) +) + +-- Builders APL +DefaultAPL:AddAPL( + BuildersAPL, + function() + return Player:GetComboPoints() < 6 or + (Player:GetComboPoints() < 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) and + (not Player:GetAuras():FindMy(ShurikenTornado):IsUp() or Player:GetEnemies(10) <= 2) + end +) + +SubModulue:Sync(function() + if Player:IsAffectingCombat() and not IsCurrentSpell(6603) then + StartAttack() + end + -- DefensivesAPL:Execute() + -- InterruptAPL:Execute() + + DefaultAPL:Execute() +end) + +Bastion:Register(SubModulue) + +local Command = Bastion.Command:New('hero') +local hrEnabled = false + +Command:Register('toggle', 'Toggle hero rotations hook', function() + hrEnabled = not hrEnabled + Bastion:Print('HeroRotation ' .. (hrEnabled and 'enabled' or 'disabled')) +end) + +local HeroHooked = false +C_Timer.NewTicker(1, function() + if HeroRotation and not HeroHooked then + hooksecurefunc(HeroRotation, "Cast", (function(Object, OffGCD, DisplayStyle, OutofRange, CustomTime) + if DisplayStyle == "Pooling" and CustomTime ~= nil then + return + end + + if not hrEnabled then + return + end + DefensivesAPL:Execute() + InterruptAPL:Execute() + + if Object.ItemUseSpell then + UseItemByName(Object.ItemName) + elseif Object.SpellName then + CastSpellByName(Object.SpellName) + end + + if IsSpellPending() == 64 then + local x, y, z = ObjectPosition("target") + Click(x, y, z) + end + + -- DevTools_Dump(Object) + -- print("HeroRotation cast " .. Object.SpellID) + + HeroHooked = true + end)) + + hooksecurefunc(HeroRotation, "CastQueue", (function(...) + if not hrEnabled then + return + end + DefensivesAPL:Execute() + InterruptAPL:Execute() + + local spellsAndItems = { ... } + + for i = 1, #spellsAndItems do + local Object = spellsAndItems[i] + + if Object.ItemUseSpell then + UseItemByName(Object.ItemName) + SpellCancelQueuedSpell() + elseif Object.SpellName then + CastSpellByName(Object.SpellName) + SpellCancelQueuedSpell() + end + + if IsSpellPending() == 64 then + local x, y, z = ObjectPosition("target") + Click(x, y, z) + end + end + + -- DevTools_Dump(Object) + -- print("HeroRotation cast " .. Object.SpellID) + end)) + + hooksecurefunc(HeroRotation, "CastQueuePooling", function(customTime, Object) + if not hrEnabled then + return + end + + DefensivesAPL:Execute() + InterruptAPL:Execute() + + if Object.ItemUseSpell then + UseItemByName(Object.ItemName) + elseif Object.SpellName then + CastSpellByName(Object.SpellName) + end + + if IsSpellPending() == 64 then + local x, y, z = ObjectPosition("target") + Click(x, y, z) + end + + -- DevTools_Dump(Object) + -- print("HeroRotation cast " .. Object.SpellID) + + HeroHooked = true + end) + HeroHooked = true + print('HeroRotation hooked') + end +end)