You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
BastionScripts/scripts/subtlety.lua

1379 lines
47 KiB

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)