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)