From 0b329a64639ee2e51ca81d77b8114d3f0310fb1b Mon Sep 17 00:00:00 2001 From: 4n0n <4n0n@tinkr.site> Date: Thu, 26 Jan 2023 14:37:14 -0600 Subject: [PATCH] Add FindAny to aura tables, update subtlety, add APL AddVariable --- scripts/subtlety.lua | 2192 +++++++++++++++++++++++++++-------- src/APL/APL.lua | 16 +- src/AuraTable/AuraTable.lua | 10 + src/Spell/Spell.lua | 5 + src/Unit/Unit.lua | 210 ++++ 5 files changed, 1951 insertions(+), 482 deletions(-) diff --git a/scripts/subtlety.lua b/scripts/subtlety.lua index 94d40e2..55c0350 100644 --- a/scripts/subtlety.lua +++ b/scripts/subtlety.lua @@ -1,3 +1,754 @@ +-- 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') + +-- 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 Stealth = Bastion.SpellBook:GetSpell(1784) +-- local PistolShot = Bastion.SpellBook:GetSpell(185763) +-- local Opportunity = Bastion.SpellBook:GetSpell(195627) +-- local SinisterStrike = Bastion.SpellBook:GetSpell(193315) +-- 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 SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) +-- 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 Audacity = Bastion.SpellBook:GetSpell(381845) +-- local Flagellation = Bastion.SpellBook:GetSpell(323654) +-- local Dreadblades = Bastion.SpellBook:GetSpell(343142) +-- 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 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 CheapShot = Bastion.SpellBook:GetSpell(1833) +-- local BagOfTricks = Bastion.SpellBook:GetSpell(312411) +-- local AutoAttack = Bastion.SpellBook:GetSpell(6603) +-- local SymbolsOfDeath = Bastion.SpellBook:GetSpell(212283) +-- local ShadowBlades = Bastion.SpellBook:GetSpell(121471) +-- 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 NumbingPoison = Bastion.SpellBook:GetSpell(5761) +-- local ShurikenStorm = Bastion.SpellBook:GetSpell(197835) +-- local BlackPowder = Bastion.SpellBook:GetSpell(319175) +-- local SecretTechnique = Bastion.SpellBook:GetSpell(280719) +-- local DarkBrew = Bastion.SpellBook:GetSpell(310454) +-- local Premeditation = Bastion.SpellBook:GetSpell(343173) +-- local DanseMacabre = Bastion.SpellBook:GetSpell(393969) + +-- local IrideusFragment = Bastion.ItemBook:GetItem(193743) +-- local Healthstone = Bastion.ItemBook:GetItem(5512) +-- local WindscarWhetstone = Bastion.ItemBook:GetItem(137486) +-- local DarkMoonRime = Bastion.ItemBook:GetItem(198477) +-- local AlgetharsPuzzleBox = Bastion.ItemBook:GetItem(193701) + +-- local RimeCards = { +-- One = Bastion.SpellBook:GetSpell(382844), +-- Two = Bastion.SpellBook:GetSpell(382845), +-- Three = Bastion.SpellBook:GetSpell(382846), +-- Four = Bastion.SpellBook:GetSpell(382847), +-- Five = Bastion.SpellBook:GetSpell(382848), +-- Six = Bastion.SpellBook:GetSpell(382849), +-- Seven = Bastion.SpellBook:GetSpell(382850), +-- Eight = Bastion.SpellBook:GetSpell(382851), +-- } + +-- 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 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 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:TimeToDie() > 12 +-- and unit:GetCombatTime() > 4 +-- then +-- target = unit +-- return true +-- end +-- end) + +-- if target == nil then +-- target = None +-- end + +-- return target +-- end) + +-- local DefaultAPL = Bastion.APL:New('default') +-- local AOEAPL = Bastion.APL:New('aoe') +-- local SpecialAPL = Bastion.APL:New('special') +-- local RacialsAPL = Bastion.APL:New('racials') + +-- local Facing = function(t) +-- return Bastion.APLTrait:New(function() +-- return Player:IsFacing(t) +-- end) +-- end + +-- SpecialAPL:AddSpell( +-- Kick:CastableIf(function(self) +-- return KickTarget:Exists() and self:IsInRange(KickTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(KickTarget) +-- ):AddTraits( +-- Facing(KickTarget) +-- ) + +-- SpecialAPL:AddSpell( +-- KidneyShot:CastableIf(function(self) +-- return KickTarget:Exists() and self:IsInRange(KickTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Kick:GetTimeSinceLastCast() > 2 and +-- (Player:GetComboPoints(Target) >= 5 or +-- ( +-- Player:GetComboPoints(Target) >= 4 and +-- (Player:GetAuras():FindMy(Broadside):IsUp() or Player:GetAuras():FindMy(Opportunity):IsUp()))) +-- and not Target:GetAuras():Find(Sanguine):IsUp() + +-- end):SetTarget(KickTarget) +-- ) + +-- SpecialAPL:AddSpell( +-- CheapShot:CastableIf(function(self) +-- return KickTarget:Exists() and self:IsInRange(KickTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(Stealth):IsUp() +-- and not Target:GetAuras():Find(Sanguine):IsUp() +-- end):SetTarget(KickTarget) +-- ) + +-- SpecialAPL:AddSpell( +-- Stealth:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and not Player:IsAffectingCombat() and +-- not Player:GetAuras():FindMy(Stealth):IsUp() and not IsMounted() +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddSpell( +-- CrimsonVial:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetHealthPercent() < 70 +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddSpell( +-- Shiv:CastableIf(function(self) +-- return PurgeTarget:Exists() and self:IsInRange(PurgeTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and PurgeTarget:GetAuras():HasAnyStealableAura() +-- end):SetTarget(PurgeTarget) +-- ) + +-- SpecialAPL:AddSpell( +-- InstantPoison:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- not Player:GetAuras():FindMy(InstantPoison):IsUp() and not Player:IsMoving() +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddSpell( +-- AtrophicPosion:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- not Player:GetAuras():FindMy(AtrophicPosion):IsUp() and not Player:IsMoving() +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddSpell( +-- NumbingPoison:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- not Player:GetAuras():FindMy(NumbingPoison):IsUp() and not Player:IsMoving() +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddItem( +-- Healthstone:UsableIf(function(self) +-- return self:IsEquippedAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetHealthPercent() < 40 +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddSpell( +-- TricksOfTheTrade:CastableIf(function(self) +-- return Tank:Exists() and self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:IsTanking(Target) +-- end):SetTarget(Tank) +-- ) + +-- SpecialAPL:AddSpell( +-- Evasion:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetHealthPercent() < 40 +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddItem( +-- IrideusFragment:UsableIf(function(self) +-- return self:IsEquippedAndUsable() and +-- not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddItem( +-- WindscarWhetstone:UsableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and +-- not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL: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()) +-- end):SetTarget(Player) +-- ) + +-- SpecialAPL:AddItem( +-- DarkMoonRime:UsableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and +-- not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) and +-- (Player:GetAuras():FindMy(RimeCards.One):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Two):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Three):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Four):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Five):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Six):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Seven):IsUp() or +-- Player:GetAuras():FindMy(RimeCards.Eight):IsUp() +-- ) +-- end):SetTarget(Target) +-- ) + +-- -- Use Shadowstrike during Shadow Dance. +-- SpecialAPL:AddSpell( +-- Shadowstrike:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(Premeditation):IsUp() and +-- Player:GetEnemies(10) <= 3 +-- end):SetTarget(Target) +-- ) + +-- RacialsAPL:AddSpell( +-- BagOfTricks:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(Target) +-- ) + +-- -- Use Symbols of Death on cooldown as much as possible. +-- DefaultAPL:AddSpell( +-- SymbolsOfDeath:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(Player):OnCast(function() +-- ShurikenTornado:Cast(Target) +-- end) +-- ) + +-- -- Use Shadow Blades on cooldown. +-- DefaultAPL:AddSpell( +-- ShadowBlades:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(Player) +-- ) + +-- -- Use Cold Blood before a finishing move, ideally before Secret Technique. +-- DefaultAPL:AddSpell( +-- ColdBlood:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetComboPoints(Target) >= 5 and SecretTechnique:IsKnownAndUsable() and +-- Player:GetAuras():FindMy(SliceAndDice):IsUp() and +-- Target:GetAuras():FindMy(Rupture):IsUp() +-- end):SetTarget(Player):OnCast(function() +-- SecretTechnique:Cast(Target) +-- end) +-- ) + +-- -- Line up Shuriken Tornado with Symbols of Death. +-- DefaultAPL:AddSpell( +-- ShurikenTornado:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() +-- end):SetTarget(Player) +-- ) + +-- -- Use Shadow Dance on cooldown as much as possible. +-- DefaultAPL:AddSpell( +-- ShadowDance:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and Gloomblade:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:GetComboPoints(Target) <= 2 +-- end):SetTarget(Player):OnCast(function() +-- Gloomblade:Cast(Target) -- We want to cast gloomblade immediately with shadow dance to trigger 1 stack of danse macabre +-- end) +-- ) + +-- -- Use Thistle Tea when low on energy. +-- -- actions.cds+=/thistle_tea,if=cooldown.symbols_of_death.remains>=3&!buff.thistle_tea.up&(energy.deficit>=100|cooldown.thistle_tea.charges_fractional>=2.75&buff.shadow_dance.up)|buff.shadow_dance.remains>=4&!buff.thistle_tea.up&spell_targets.shuriken_storm>=3|!buff.thistle_tea.up&fight_remains<=(6*cooldown.thistle_tea.charges) +-- DefaultAPL:AddSpell( +-- ThistleTea:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetPowerDeficit() >= 100 and +-- ThistleTea:GetTimeSinceLastCast() >= 3 +-- end):SetTarget(Player) + +-- ) + +-- -- Use Finishing moves with 6 or more combo points (5 or more during Shadow Dance) with the following priority: +-- -- Cast Slice and Dice if it needs to be refreshed for maintenance or if it is not up. +-- DefaultAPL:AddSpell( +-- SliceAndDice:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 6 or +-- (Player:GetComboPoints(Target) >= 5 and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) and +-- ( +-- not Player:GetAuras():FindMy(SliceAndDice):IsUp() or +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 12 +-- ) +-- end):SetTarget(Player) +-- ) + +-- -- Cast Rupture if it needs to be refreshed for maintenance or if it is not up. +-- DefaultAPL:AddSpell( +-- Rupture:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 6 or +-- (Player:GetComboPoints(Target) >= 5 and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) and ( +-- not Target:GetAuras():FindMy(Rupture):IsUp() or +-- Target:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 +-- ) and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Target) +-- ) + +-- DefaultAPL:AddSpell( +-- SecretTechnique:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 5) and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and +-- (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or +-- not DanseMacabre:IsKnown()) and +-- (not ColdBlood:IsKnown() or +-- ColdBlood:GetCooldownRemaining() > Player:GetAuras():FindMy(ShadowDanceAura):GetRemainingTime() - 2) +-- end):SetTarget(Target) +-- ) + +-- -- Cast Eviscerate if it is available. +-- DefaultAPL:AddSpell( +-- Eviscerate:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 6 or +-- (Player:GetComboPoints(Target) >= 5 and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) +-- end):SetTarget(Target) +-- ) + +-- -- Vanish - Is a fairly weak cooldown. It is best to use on low combo points for a Shadowstrike cast. Use it after Secret Technique in Shadow Dance when playing with Danse Macabre. +-- DefaultAPL:AddSpell( +-- Vanish:CastableIf(function(self) +-- return Tank:Exists() and Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetComboPoints(Target) < 4 +-- end):SetTarget(Player) +-- ) + +-- -- Use Combo Point builder with the following priority: +-- -- Use Gloomblade outside of Shadow Dance. +-- DefaultAPL:AddSpell( +-- Gloomblade:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Target) +-- ) + +-- -- Use Shadowstrike during Shadow Dance. +-- DefaultAPL:AddSpell( +-- Shadowstrike:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Target) +-- ) + +-- -- AOE + +-- -- Use Symbols of Death on cooldown as much as possible. +-- AOEAPL:AddSpell( +-- SymbolsOfDeath:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(Player):OnCast(function() +-- ShurikenTornado:Cast(Target) +-- end) +-- ) + +-- -- Use Shadow Blades on cooldown. +-- AOEAPL:AddSpell( +-- ShadowBlades:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() +-- end):SetTarget(Player) +-- ) + +-- -- Use Cold Blood before a finishing move. +-- AOEAPL:AddSpell( +-- ColdBlood:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetComboPoints(Target) >= 5 and SecretTechnique:IsKnownAndUsable() and +-- Player:GetAuras():FindMy(SliceAndDice):IsUp() and +-- Target:GetAuras():FindMy(Rupture):IsUp() +-- end):SetTarget(Player):OnCast(function() +-- SecretTechnique:Cast(Target) +-- end) +-- ) + +-- -- Line up Shuriken Tornado with Symbols of Death. +-- AOEAPL:AddSpell( +-- ShurikenTornado:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() +-- end):SetTarget(Target) +-- ) + +-- -- Use Shadow Dance on cooldown as much as possible. +-- AOEAPL:AddSpell( +-- ShadowDance:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and Gloomblade:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:GetComboPoints(Target) <= 2 +-- end):SetTarget(Player):OnCast(function() +-- Gloomblade:Cast(Target) -- We want to cast gloomblade immediately with shadow dance to trigger 1 stack of danse macabre +-- end) +-- ) + +-- -- Use Thistle Tea with Shadow Dance. +-- AOEAPL:AddSpell( +-- ThistleTea:CastableIf(function(self) +-- return Target:Exists() and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetPowerDeficit() >= 100 and +-- ThistleTea:GetTimeSinceLastCast() >= 3 +-- end):SetTarget(Player) +-- ) + +-- -- Use Finishing moves with 5 or more combo points with the following priority: +-- -- Cast Slice and Dice if it needs to be refreshed for maintenance or if it is not up. +-- AOEAPL:AddSpell( +-- SliceAndDice:CastableIf(function(self) +-- return Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 5) and +-- ( +-- not Player:GetAuras():FindMy(SliceAndDice):IsUp() or +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 6 +-- ) +-- and Player:GetEnemies(10) < 6 +-- end):SetTarget(Target) +-- ) + +-- -- Cast Rupture if it needs to be refreshed for maintenance or if it is not up. +-- AOEAPL:AddSpell( +-- Rupture:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 5) and ( +-- not Target:GetAuras():FindMy(Rupture):IsUp() or +-- Target:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 +-- ) +-- and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() +-- and not Player:GetAuras():FindMy(ThistleTea):IsUp() +-- end):SetTarget(Target) +-- ) + +-- -- Cast Rupture on all targets. (scam??) +-- AOEAPL:AddSpell( +-- Rupture:CastableIf(function(self) +-- return RuptureTarget:Exists() and self:IsInRange(RuptureTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(RuptureTarget) >= 6) and ( +-- not RuptureTarget:GetAuras():FindMy(Rupture):IsUp() or +-- RuptureTarget:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 +-- ) +-- and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(RuptureTarget) +-- ) + +-- -- actions.finish+=/secret_technique,if=buff.shadow_dance.up&(buff.danse_macabre.stack>=3|!talent.danse_macabre)&(!talent.cold_blood|cooldown.cold_blood.remains>buff.shadow_dance.remains-2) +-- AOEAPL:AddSpell( +-- SecretTechnique:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 5) and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and +-- (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or +-- not DanseMacabre:IsKnown()) and +-- (not ColdBlood:IsKnown() or +-- ColdBlood:GetCooldownRemaining() > Player:GetAuras():FindMy(ShadowDanceAura):GetRemainingTime() - 2) +-- end):SetTarget(Target) +-- ) + +-- -- Cast Black Powder with 3 or more targets, 2 or more when talented into Dark Brew. +-- AOEAPL:AddSpell( +-- BlackPowder:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- (Player:GetComboPoints(Target) >= 5) and +-- (Player:GetEnemies(10) >= 3 or +-- (Player:GetEnemies(10) >= 2 and +-- DarkBrew:IsKnown())) +-- end):SetTarget(Target) +-- ) + +-- -- Cast Eviscerate. +-- AOEAPL:AddSpell( +-- Eviscerate:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetComboPoints(Target) >= 5 +-- end):SetTarget(Target) +-- ) + +-- -- Vanish - Is a fairly weak cooldown. It is best to use on low combo points for a Shadowstrike cast. Use it after Secret Technique in Shadow Dance when playing with Danse Macabre. +-- AOEAPL:AddSpell( +-- Vanish:CastableIf(function(self) +-- return Tank:Exists() and Target:Exists() and Player:InMelee(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetComboPoints(Target) < 4 +-- end):SetTarget(Player) +-- ) + +-- -- Use Combo Point builder with the following priority: +-- -- Use Shuriken Storm on 2 targets outside of Shadow Dance. +-- AOEAPL:AddSpell( +-- ShurikenStorm:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetEnemies(10) == 2 and +-- not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Player) +-- ) + +-- -- Use Shadowstrike on 2 and 3 targets during Shadow Dance or to proc Premeditation. +-- AOEAPL:AddSpell( +-- Shadowstrike:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetEnemies(10) >= 2 and Player:GetEnemies(10) <= 3 and +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- end):SetTarget(Target) +-- ) + +-- -- Use Shuriken Storm at > 2 targets. +-- AOEAPL:AddSpell( +-- ShurikenStorm:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetEnemies(10) > 2 +-- end):SetTarget(Player) +-- ) + +-- -- Use gloomblade at <= 2 targets. +-- AOEAPL:AddSpell( +-- Gloomblade:CastableIf(function(self) +-- return Target:Exists() and self:IsInRange(Target) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetEnemies(10) <= 2 +-- end):SetTarget(Target) +-- ) + +-- SubModulue:Sync(function() +-- SpecialAPL:Execute() +-- if Player:GetEnemies(10) >= 2 then +-- AOEAPL:Execute() +-- else +-- DefaultAPL:Execute() +-- end +-- RacialsAPL:Execute() +-- end) + +-- Bastion:Register(SubModulue) + local Tinkr, Bastion = ... local SubModulue = Bastion.Module:New('sub') @@ -6,60 +757,92 @@ local Player = Bastion.UnitManager:Get('player') local None = Bastion.UnitManager:Get('none') local Target = Bastion.UnitManager:Get('target') -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 Stealth = Bastion.SpellBook:GetSpell(1784) -local PistolShot = Bastion.SpellBook:GetSpell(185763) -local Opportunity = Bastion.SpellBook:GetSpell(195627) -local SinisterStrike = Bastion.SpellBook:GetSpell(193315) -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 SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) -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 Audacity = Bastion.SpellBook:GetSpell(381845) -local Flagellation = Bastion.SpellBook:GetSpell(323654) -local Dreadblades = Bastion.SpellBook:GetSpell(343142) -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 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 CheapShot = Bastion.SpellBook:GetSpell(1833) -local BagOfTricks = Bastion.SpellBook:GetSpell(312411) -local AutoAttack = Bastion.SpellBook:GetSpell(6603) -local SymbolsOfDeath = Bastion.SpellBook:GetSpell(212283) -local ShadowBlades = Bastion.SpellBook:GetSpell(121471) -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 NumbingPoison = Bastion.SpellBook:GetSpell(5761) -local ShurikenStorm = Bastion.SpellBook:GetSpell(197835) -local BlackPowder = Bastion.SpellBook:GetSpell(319175) -local SecretTechnique = Bastion.SpellBook:GetSpell(280719) -local DarkBrew = Bastion.SpellBook:GetSpell(310454) -local Premeditation = Bastion.SpellBook:GetSpell(343173) -local DanseMacabre = Bastion.SpellBook:GetSpell(393969) +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 GrandMelee = Bastion.SpellBook:GetSpell(193358) +local Broadside = Bastion.SpellBook:GetSpell(193356) +local TrueBearing = Bastion.SpellBook:GetSpell(193359) +local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) +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 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 FinalityRupture = Bastion.SpellBook:GetSpell(385951) +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(385722) +local FindWeakness = Bastion.SpellBook:GetSpell(91023) +local ImprovedShurikenStorm = Bastion.SpellBook:GetSpell(319951) + + local IrideusFragment = Bastion.ItemBook:GetItem(193743) local Healthstone = Bastion.ItemBook:GetItem(5512) @@ -207,544 +990,991 @@ local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function() end) local DefaultAPL = Bastion.APL:New('default') -local AOEAPL = Bastion.APL:New('aoe') -local SpecialAPL = Bastion.APL:New('special') -local RacialsAPL = Bastion.APL:New('racials') +local CDsAPL = Bastion.APL:New('cds') +local StealthedAPL = Bastion.APL:New('stealthed') +local StealthCDsAPL = Bastion.APL:New('stealthed_cds') +local FinishAPL = Bastion.APL:New('finish') +local BuildAPL = Bastion.APL:New('build') +local ItemsAPL = Bastion.APL:New('items') + +ItemsAPL:AddItem( + IrideusFragment:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) +) -local Facing = function(t) - return Bastion.APLTrait:New(function() - return Player:IsFacing(t) - end) -end +ItemsAPL:AddItem( + WindscarWhetstone:UsableIf(function(self) + return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) +) -SpecialAPL:AddSpell( - Kick:CastableIf(function(self) - return KickTarget:Exists() and self:IsInRange(KickTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(KickTarget) -):AddTraits( - Facing(KickTarget) +ItemsAPL: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()) + end):SetTarget(Player) ) -SpecialAPL:AddSpell( - KidneyShot:CastableIf(function(self) - return KickTarget:Exists() and self:IsInRange(KickTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Kick:GetTimeSinceLastCast() > 2 and - (Player:GetComboPoints(Target) >= 5 or - ( - Player:GetComboPoints(Target) >= 4 and - (Player:GetAuras():FindMy(Broadside):IsUp() or Player:GetAuras():FindMy(Opportunity):IsUp()))) - and not Target:GetAuras():Find(Sanguine):IsUp() +ItemsAPL:AddItem( + DarkMoonRime:UsableIf(function(self) + return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) and + (Player:GetAuras():FindMy(RimeCards.One):IsUp() or + Player:GetAuras():FindMy(RimeCards.Two):IsUp() or + Player:GetAuras():FindMy(RimeCards.Three):IsUp() or + Player:GetAuras():FindMy(RimeCards.Four):IsUp() or + Player:GetAuras():FindMy(RimeCards.Five):IsUp() or + Player:GetAuras():FindMy(RimeCards.Six):IsUp() or + Player:GetAuras():FindMy(RimeCards.Seven):IsUp() or + Player:GetAuras():FindMy(RimeCards.Eight):IsUp() + ) + end):SetTarget(Target) +) - end):SetTarget(KickTarget) +-- # 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) ) -SpecialAPL:AddSpell( - CheapShot:CastableIf(function(self) +-- # Interrupt on cooldown to allow simming interactions with that +-- actions+=/kick +DefaultAPL:AddSpell( + Kick:CastableIf(function(self) return KickTarget:Exists() and self:IsInRange(KickTarget) and self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(Stealth):IsUp() - and not Target:GetAuras():Find(Sanguine):IsUp() + not Player:IsCastingOrChanneling() and Player:IsFacing(Target) end):SetTarget(KickTarget) ) -SpecialAPL:AddSpell( - Stealth:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and not Player:IsAffectingCombat() and - not Player:GetAuras():FindMy(Stealth):IsUp() and not IsMounted() - end):SetTarget(Player) +-- double consume_cp_max() const +-- { +-- return COMBO_POINT_MAX + as( talent.rogue.deeper_stratagem->effectN( 2 ).base_value() + +-- talent.outlaw.devious_stratagem->effectN( 2 ).base_value() + +-- talent.subtlety.secret_stratagem->effectN( 2 ).base_value() ); +-- } + +-- # Used to determine whether cooldowns wait for SnD based on targets. +-- actions+=/variable,name=snd_condition,value=buff.slice_and_dice.up|spell_targets.shuriken_storm>=cp_max_spend +DefaultAPL:AddVariable( + 'snd_condition', + function() + return Player:GetAuras():FindMy(SliceAndDice):IsUp() or + Player:GetEnemies(10) >= Player:GetComboPointsMax() + end ) -SpecialAPL:AddSpell( - CrimsonVial:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 70 - end):SetTarget(Player) -) +-- # Check to see if the next CP (in the event of a ShT proc) is Animacharged +-- actions+=/variable,name=is_next_cp_animacharged,if=talent.echoing_reprimand.enabled,value=combo_points=1&buff.echoing_reprimand_2.up|combo_points=2&buff.echoing_reprimand_3.up|combo_points=3&buff.echoing_reprimand_4.up|combo_points=4&buff.echoing_reprimand_5.up +DefaultAPL:AddVariable( + 'is_next_cp_animacharged', + function() + if not EchoingReprimand:IsKnown() then + return false + end -SpecialAPL:AddSpell( - Shiv:CastableIf(function(self) - return PurgeTarget:Exists() and self:IsInRange(PurgeTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and PurgeTarget:GetAuras():HasAnyStealableAura() - end):SetTarget(PurgeTarget) + local comboPoints = Player:GetComboPoints() + + if comboPoints == 1 and Player:GetAuras():FindMy(EchoingReprimand2):IsUp() then + return true + end + + if comboPoints == 2 and Player:GetAuras():FindMy(EchoingReprimand3):IsUp() then + return true + end + + if comboPoints == 3 and Player:GetAuras():FindMy(EchoingReprimand4):IsUp() then + return true + end + + if comboPoints == 4 and Player:GetAuras():FindMy(EchoingReprimand5):IsUp() then + return true + end + + return false + end ) -SpecialAPL:AddSpell( - InstantPoison:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - not Player:GetAuras():FindMy(InstantPoison):IsUp() and not Player:IsMoving() - end):SetTarget(Player) +-- # Account for ShT reaction time by ignoring low-CP animacharged matches in the 0.5s preceeding a potential ShT proc +-- actions+=/variable,name=effective_combo_points,value=effective_combo_points +DefaultAPL:AddVariable( + 'effective_combo_points', + function() + local cur = Player:GetComboPoints() or 0 + if not EchoingReprimand:IsKnown() then + return cur + end + + if cur < 2 or cur > 5 then + return cur + end + + if Player:GetAuras():FindMy(EchoingReprimand):IsUp() or Player:GetAuras():FindMy(EchoingReprimand2):IsUp() or + Player:GetAuras():FindMy(EchoingReprimand3):IsUp() or + Player:GetAuras():FindMy(EchoingReprimand4):IsUp() or + Player:GetAuras():FindMy(EchoingReprimand5):IsUp() + then + return 7 + end + + return cur + end ) -SpecialAPL:AddSpell( - AtrophicPosion:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - not Player:GetAuras():FindMy(AtrophicPosion):IsUp() and not Player:IsMoving() - end):SetTarget(Player) +-- actions+=/variable,name=effective_combo_points,if=talent.echoing_reprimand.enabled&effective_combo_points>combo_points&combo_points.deficit>2&time_to_sht.4.plus<0.5&!variable.is_next_cp_animacharged,value=combo_points +DefaultAPL:AddVariable( + 'effective_combo_points', + function() + if not EchoingReprimand:IsKnown() then + return 0 + end + + local cur = Player:GetComboPoints() or 0 + local deficit = Player:GetComboPointsDeficit() or 0 + + if cur > Player:GetComboPoints() and deficit > 2 and + Player:GetAuras():FindMy(EchoingReprimand4):GetRemainingTime() < 0.5 and + not DefaultAPL:GetVariable('is_next_cp_animacharged') + then + return cur + end + + return 0 + end ) -SpecialAPL:AddSpell( - NumbingPoison:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - not Player:GetAuras():FindMy(NumbingPoison):IsUp() and not Player:IsMoving() - end):SetTarget(Player) +-- # Check CDs at first +-- actions+=/call_action_list,name=cds +DefaultAPL:AddAPL( + CDsAPL, + function() + return true + end ) -SpecialAPL:AddItem( - Healthstone:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 40 - end):SetTarget(Player) +-- # Apply Slice and Dice at 4+ CP if it expires within the next GCD or is not up +-- actions+=/slice_and_dice,if=spell_targets.shuriken_storm6&combo_points>=4 +DefaultAPL:AddSpell( + SliceAndDice:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetEnemies(10) < Player:GetComboPointsMax() and + Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < Player:GetGCD() and + Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() > 6 and + Player:GetComboPoints() >= 4 + end + ):SetTarget(Player) ) -SpecialAPL:AddSpell( - TricksOfTheTrade:CastableIf(function(self) - return Tank:Exists() and self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:IsTanking(Target) - end):SetTarget(Tank) +-- # Run fully switches to the Stealthed Rotation (by doing so, it forces pooling if nothing is available). +-- actions+=/run_action_list,name=stealthed,if=stealthed.all +DefaultAPL:AddAPL( + StealthedAPL, + function() + return Player:GetAuras():FindMy(Stealth):IsUp() or Player:GetAuras():FindMy(Subterfuge):IsUp() or + Player:GetAuras():FindMy(ShadowDanceAura):IsUp() + end ) -SpecialAPL:AddSpell( - Evasion:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 40 - end):SetTarget(Player) +-- # Only change rotation if we have priority_rotation set. +-- actions+=/variable,name=priority_rotation,value=priority_rotation +DefaultAPL:AddVariable( + 'priority_rotation', + function() + return false + end ) -SpecialAPL:AddItem( - IrideusFragment:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) - end):SetTarget(Player) +-- # Used to define when to use stealth CDs or builders +-- actions+=/variable,name=stealth_threshold,value=25+talent.vigor.enabled*20+talent.master_of_shadows.enabled*20+talent.shadow_focus.enabled*25+talent.alacrity.enabled*20+25*(spell_targets.shuriken_storm>=4) +DefaultAPL:AddVariable( + 'stealth_threshold', + function() + return 25 + (Vigor:IsKnown() and 20 or 0) + (MasterOfShadows:IsKnown() and 20 or 0) + + (ShadowFocus:IsKnown() and 25 or 0) + (Alacrity:IsKnown() and 20 or 0) + + (25 * (Player:GetEnemies(10) >= 4 and 1 or 0)) + end ) -SpecialAPL:AddItem( - WindscarWhetstone:UsableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) - end):SetTarget(Player) +-- # Consider using a Stealth CD when reaching the energy threshold +-- actions+=/call_action_list,name=stealth_cds,if=energy.deficit<=variable.stealth_threshold +DefaultAPL:AddAPL( + StealthCDsAPL, + function() + return Player:GetPowerDeficit() <= DefaultAPL:GetVariable('stealth_threshold') + end ) -SpecialAPL: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()) - end):SetTarget(Player) +-- actions+=/call_action_list,name=finish,if=variable.effective_combo_points>=cp_max_spend +DefaultAPL:AddAPL( + FinishAPL, + function() + return DefaultAPL:GetVariable('effective_combo_points') >= Player:GetComboPointsMax() + end ) -SpecialAPL:AddItem( - DarkMoonRime:UsableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) and - (Player:GetAuras():FindMy(RimeCards.One):IsUp() or - Player:GetAuras():FindMy(RimeCards.Two):IsUp() or - Player:GetAuras():FindMy(RimeCards.Three):IsUp() or - Player:GetAuras():FindMy(RimeCards.Four):IsUp() or - Player:GetAuras():FindMy(RimeCards.Five):IsUp() or - Player:GetAuras():FindMy(RimeCards.Six):IsUp() or - Player:GetAuras():FindMy(RimeCards.Seven):IsUp() or - Player:GetAuras():FindMy(RimeCards.Eight):IsUp() - ) - end):SetTarget(Target) +-- # Finish at maximum or close to maximum combo point value +-- actions+=/call_action_list,name=finish,if=combo_points.deficit<=1+buff.the_rotten.up|fight_remains<=1&variable.effective_combo_points>=3 +DefaultAPL:AddAPL( + FinishAPL, + function() + return Player:GetComboPointsDeficit() <= 1 + (((Player:GetAuras():FindMy(TheRotten):IsUp()) or + (Player:TimeToDie() <= 1 and DefaultAPL:GetVariable('effective_combo_points') >= 3)) and 1 or 0) + end ) --- Use Shadowstrike during Shadow Dance. -SpecialAPL:AddSpell( - Shadowstrike:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(Premeditation):IsUp() and - Player:GetEnemies(10) <= 3 - end):SetTarget(Target) +-- # Finish at 4+ against 4 targets (outside stealth) +-- actions+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=(4-talent.seal_fate)&variable.effective_combo_points>=4 +DefaultAPL:AddAPL( + FinishAPL, + function() + return Player:GetEnemies(10) >= (4 - (SealFate:IsKnown() and 1 or 0)) and + DefaultAPL:GetVariable('effective_combo_points') >= 4 + end ) -RacialsAPL:AddSpell( - BagOfTricks:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(Target) +-- # Use a builder when reaching the energy threshold +-- actions+=/call_action_list,name=build,if=energy.deficit<=variable.stealth_threshold +DefaultAPL:AddAPL( + BuildAPL, + function() + return Player:GetPowerDeficit() <= DefaultAPL:GetVariable('stealth_threshold') + end ) --- Use Symbols of Death on cooldown as much as possible. +-- # Lowest priority in all of the APL because it causes a GCD +-- actions+=/arcane_torrent,if=energy.deficit>=15+energy.regen DefaultAPL:AddSpell( - SymbolsOfDeath:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(Player):OnCast(function() - ShurikenTornado:Cast(Target) - end) + ArcaneTorrent:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:InMelee(Target) and + Player:GetPowerDeficit() >= 15 + Player:GetPowerRegen() + end + ):SetTarget(Player) ) --- Use Shadow Blades on cooldown. +-- actions+=/arcane_pulse DefaultAPL:AddSpell( - ShadowBlades:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(Player) + ArcanePulse:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and true + end + ):SetTarget(Target) ) --- Use Cold Blood before a finishing move, ideally before Secret Technique. +-- actions+=/lights_judgment DefaultAPL:AddSpell( - ColdBlood:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetComboPoints(Target) >= 5 and SecretTechnique:IsKnownAndUsable() and - Player:GetAuras():FindMy(SliceAndDice):IsUp() and - Target:GetAuras():FindMy(Rupture):IsUp() - end):SetTarget(Player):OnCast(function() - SecretTechnique:Cast(Target) - end) + LightsJudgment:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and true + end + ):SetTarget(Target) ) --- Line up Shuriken Tornado with Symbols of Death. +-- actions+=/bag_of_tricks DefaultAPL:AddSpell( - ShurikenTornado:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() - end):SetTarget(Player) + BagOfTricks:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and true + end + ):SetTarget(Target) ) --- Use Shadow Dance on cooldown as much as possible. -DefaultAPL:AddSpell( - ShadowDance:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and Gloomblade:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:GetComboPoints(Target) <= 2 - end):SetTarget(Player):OnCast(function() - Gloomblade:Cast(Target) -- We want to cast gloomblade immediately with shadow dance to trigger 1 stack of danse macabre - end) +-- # Builders +-- actions.build=shuriken_storm,if=spell_targets>=2+(buff.lingering_shadow.remains>=6|buff.perforated_veins.up) +BuildAPL:AddSpell( + ShurikenStorm:CastableIf( + function(self) + return self:IsKnownAndUsable() and + Player:GetEnemies(10) >= 2 + ((Player:GetAuras():FindMy(LingeringShadow):GetRemainingTime() >= 6 or + Player:GetAuras():FindMy(PerforatedVeins):IsUp()) and 1 or 0) + end + ):SetTarget(Target) ) --- Use Thistle Tea when low on energy. --- actions.cds+=/thistle_tea,if=cooldown.symbols_of_death.remains>=3&!buff.thistle_tea.up&(energy.deficit>=100|cooldown.thistle_tea.charges_fractional>=2.75&buff.shadow_dance.up)|buff.shadow_dance.remains>=4&!buff.thistle_tea.up&spell_targets.shuriken_storm>=3|!buff.thistle_tea.up&fight_remains<=(6*cooldown.thistle_tea.charges) -DefaultAPL:AddSpell( - ThistleTea:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetPowerDeficit() >= 100 and - ThistleTea:GetTimeSinceLastCast() >= 3 - end):SetTarget(Player) +-- # Build immediately unless the next CP is Animacharged and we won't cap energy waiting for it. +-- actions.build+=/variable,name=anima_helper,value=!talent.echoing_reprimand.enabled|!(variable.is_next_cp_animacharged&(time_to_sht.3.plus<0.5|time_to_sht.4.plus<1)&energy<60) +BuildAPL:AddVariable( + 'anima_helper', + function() + return not EchoingReprimand:IsKnown() or (not (DefaultAPL:GetVariable('is_next_cp_animacharged') and + (Player:GetTimeToShurikenTornado(3) < 0.5 or Player:GetTimeToShurikenTornado(4) < 1) and + Player:GetPower() < 60)) + end +) +-- actions.build+=/gloomblade,if=variable.anima_helper +BuildAPL:AddSpell( + Gloomblade:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and BuildAPL:GetVariable('anima_helper') + end + ):SetTarget(Target) ) --- Use Finishing moves with 6 or more combo points (5 or more during Shadow Dance) with the following priority: --- Cast Slice and Dice if it needs to be refreshed for maintenance or if it is not up. -DefaultAPL:AddSpell( - SliceAndDice:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 6 or - (Player:GetComboPoints(Target) >= 5 and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) and - ( - not Player:GetAuras():FindMy(SliceAndDice):IsUp() or - Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 12 - ) - end):SetTarget(Player) +-- actions.build+=/backstab,if=variable.anima_helper +BuildAPL:AddSpell( + Backstab:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and BuildAPL:GetVariable('anima_helper') + end + ):SetTarget(Target) ) --- Cast Rupture if it needs to be refreshed for maintenance or if it is not up. -DefaultAPL:AddSpell( - Rupture:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 6 or - (Player:GetComboPoints(Target) >= 5 and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) and ( - not Target:GetAuras():FindMy(Rupture):IsUp() or - Target:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 - ) and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(Target) +-- # Cooldowns Use Dance off-gcd before the first Shuriken Storm from Tornado comes in. +-- actions.cds=shadow_dance,use_off_gcd=1,if=!buff.shadow_dance.up&buff.shuriken_tornado.up&buff.shuriken_tornado.remains<=3.5 +CDsAPL:AddSpell( + ShadowDance:CastableIf( + function(self) + return Player:IsAffectingCombat() and self:IsKnownAndUsable() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + Player:GetAuras():FindMy(ShurikenTornado):GetRemainingTime() <= 3.5 + end + ):SetTarget(Player) ) -DefaultAPL:AddSpell( - SecretTechnique:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 5) and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and - (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or - not DanseMacabre:IsKnown()) and - (not ColdBlood:IsKnown() or - ColdBlood:GetCooldownRemaining() > Player:GetAuras():FindMy(ShadowDanceAura):GetRemainingTime() - 2) - end):SetTarget(Target) +-- # (Unless already up because we took Shadow Focus) use Symbols off-gcd before the first Shuriken Storm from Tornado comes in. +-- actions.cds+=/symbols_of_death,use_off_gcd=1,if=buff.shuriken_tornado.up&buff.shuriken_tornado.remains<=3.5 +CDsAPL:AddSpell( + SymbolsOfDeath:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + Player:GetAuras():FindMy(ShurikenTornado):GetRemainingTime() <= 3.5 + end + ):SetTarget(Player) ) --- Cast Eviscerate if it is available. -DefaultAPL:AddSpell( - Eviscerate:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 6 or - (Player:GetComboPoints(Target) >= 5 and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp())) - end):SetTarget(Target) +-- # Vanish for Shadowstrike with Danse Macabre at adaquate stacks +-- actions.cds+=/vanish,if=buff.danse_macabre.stack>3&combo_points<=2 +CDsAPL:AddSpell( + Vanish:CastableIf( + function(self) + return Player:IsAffectingCombat() and Player:GetAuras():FindMy(Vanish):IsUp() and self:IsKnownAndUsable() and + Player:GetAuras():FindMy(DanseMacabre):GetCount() > 3 and + DefaultAPL:GetVariable('effective_combo_points') <= 2 + end + ):SetTarget(Player) ) --- Vanish - Is a fairly weak cooldown. It is best to use on low combo points for a Shadowstrike cast. Use it after Secret Technique in Shadow Dance when playing with Danse Macabre. -DefaultAPL:AddSpell( - Vanish:CastableIf(function(self) - return Tank:Exists() and Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetComboPoints(Target) < 4 - end):SetTarget(Player) +-- # Cold Blood on 5 combo points when not playing Secret Technique +-- actions.cds+=/cold_blood,if=!talent.secret_technique&combo_points>=5 +CDsAPL:AddSpell( + ColdBlood:CastableIf( + function(self) + return self:IsKnownAndUsable() and not SecretTechnique:IsKnown() and + DefaultAPL:GetVariable('effective_combo_points') >= 5 + end + ):SetTarget(Player) ) --- Use Combo Point builder with the following priority: --- Use Gloomblade outside of Shadow Dance. -DefaultAPL:AddSpell( - Gloomblade:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(Target) +-- actions.cds+=/flagellation,target_if=max:target.time_to_die,if=variable.snd_condition&combo_points>=5&target.time_to_die>10 +CDsAPL:AddSpell( + Flagellation:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and DefaultAPL:GetVariable('snd_condition') and + DefaultAPL:GetVariable('effective_combo_points') >= 5 and + Target:TimeToDie() > 10 + end + ):SetTarget(Target) ) --- Use Shadowstrike during Shadow Dance. -DefaultAPL:AddSpell( - Shadowstrike:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(Target) +-- # Pool for Tornado pre-SoD with ShD ready when not running SF. +-- actions.cds+=/pool_resource,for_next=1,if=talent.shuriken_tornado.enabled&!talent.shadow_focus.enabled + + +-- # Use Tornado pre SoD when we have the energy whether from pooling without SF or just generally. +-- actions.cds+=/shuriken_tornado,if=spell_targets.shuriken_storm<=1&energy>=60&variable.snd_condition&cooldown.symbols_of_death.up&cooldown.shadow_dance.charges>=1&(!talent.flagellation.enabled&!cooldown.flagellation.up|buff.flagellation_buff.up|spell_targets.shuriken_storm>=5)&combo_points<=2&!buff.premeditation.up +CDsAPL:AddSpell( + ShurikenTornado:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetEnemies(10) <= 1 and + Player:GetPower() >= 60 and + DefaultAPL:GetVariable('snd_condition') and + SymbolsOfDeath:OnCooldown() and + ShadowDance:GetCharges() >= 1 and + (not Flagellation:IsKnown() and not Flagellation:OnCooldown() or + Player:GetAuras():FindMy(Flagellation):IsUp() or + Player:GetEnemies(10) >= 5) and + DefaultAPL:GetVariable('effective_combo_points') <= 2 and + not Player:GetAuras():FindMy(Premeditation):IsUp() + end + ):SetTarget(Target) ) --- AOE +-- actions.cds+=/sepsis,if=variable.snd_condition&combo_points.deficit>=1&target.time_to_die>=16 +CDsAPL:AddSpell( + Sepsis:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and DefaultAPL:GetVariable('snd_condition') and + Player:GetComboPointsDeficit() >= 1 and + Target:TimeToDie() >= 16 + end + ):SetTarget(Target) +) --- Use Symbols of Death on cooldown as much as possible. -AOEAPL:AddSpell( - SymbolsOfDeath:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(Player):OnCast(function() - ShurikenTornado:Cast(Target) - end) +-- # Use Symbols on cooldown (after first SnD) unless we are going to pop Tornado and do not have Shadow Focus. +-- actions.cds+=/symbols_of_death,if=variable.snd_condition&(!talent.flagellation|cooldown.flagellation.remains>10|cooldown.flagellation.up&combo_points>=5) +CDsAPL:AddSpell( + SymbolsOfDeath:CastableIf( + function(self) + return self:IsKnownAndUsable() and DefaultAPL:GetVariable('snd_condition') and + (not Flagellation:IsKnown() or + Flagellation:GetCooldownRemaining() > 10 or + Flagellation:OnCooldown() and DefaultAPL:GetVariable('effective_combo_points') >= 5) + end + ):SetTarget(Player) ) --- Use Shadow Blades on cooldown. -AOEAPL:AddSpell( - ShadowBlades:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() - end):SetTarget(Player) +-- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or not stealthed 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) +-- # 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 + +-- actions.cds+=/shadow_blades,if=variable.snd_condition&combo_points.deficit>=2&target.time_to_die>=10&(dot.sepsis.ticking|cooldown.sepsis.remains<=8|!talent.sepsis)|fight_remains<=20 +CDsAPL:AddSpell( + ShadowBlades:CastableIf( + function(self) + return Player:IsAffectingCombat() and self:IsKnownAndUsable() and + DefaultAPL:GetVariable('snd_condition') and + Player:GetComboPointsDeficit() >= 2 and + Target:TimeToDie() >= 10 and + ((Target:GetAuras():FindMy(Sepsis):IsUp() or + Sepsis:GetCooldownRemaining() <= 8 or + not Sepsis:IsKnown()) or + Target:TimeToDie() <= 20) + end + ):SetTarget(Target) ) --- Use Cold Blood before a finishing move. -AOEAPL:AddSpell( - ColdBlood:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetComboPoints(Target) >= 5 and SecretTechnique:IsKnownAndUsable() and - Player:GetAuras():FindMy(SliceAndDice):IsUp() and - Target:GetAuras():FindMy(Rupture):IsUp() - end):SetTarget(Player):OnCast(function() - SecretTechnique:Cast(Target) - end) +-- actions.cds+=/echoing_reprimand,if=variable.snd_condition&combo_points.deficit>=3&(variable.priority_rotation|spell_targets.shuriken_storm<=4|talent.resounding_clarity)&(buff.shadow_dance.up|!talent.danse_macabre) +CDsAPL:AddSpell( + EchoingReprimand:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and DefaultAPL:GetVariable('snd_condition') and + Player:GetComboPointsDeficit() >= 3 and + (DefaultAPL:GetVariable('priority_rotation') or + Player:GetEnemies(10) <= 4 or + ResoundingClarity:IsKnown()) and + (Player:GetAuras():FindMy(ShadowDance):IsUp() or + not DanseMacabre:IsKnown()) + end + ):SetTarget(Target) ) --- Line up Shuriken Tornado with Symbols of Death. -AOEAPL:AddSpell( - ShurikenTornado:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() - end):SetTarget(Target) +-- # With SF, if not already done, use Tornado with SoD up. +-- actions.cds+=/shuriken_tornado,if=variable.snd_condition&buff.symbols_of_death.up&combo_points<=2&(!buff.premeditation.up|spell_targets.shuriken_storm>4) +CDsAPL:AddSpell( + ShurikenTornado:CastableIf( + function(self) + return self:IsKnownAndUsable() and DefaultAPL:GetVariable('snd_condition') and + Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and + DefaultAPL:GetVariable('effective_combo_points') <= 2 and + (not Player:GetAuras():FindMy(Premeditation):IsUp() or + Player:GetEnemies(10) > 4) + end + ):SetTarget(Target) ) --- Use Shadow Dance on cooldown as much as possible. -AOEAPL:AddSpell( - ShadowDance:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and Gloomblade:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:GetComboPoints(Target) <= 2 - end):SetTarget(Player):OnCast(function() - Gloomblade:Cast(Target) -- We want to cast gloomblade immediately with shadow dance to trigger 1 stack of danse macabre - end) +-- actions.cds+=/shuriken_tornado,if=cooldown.shadow_dance.ready&!stealthed.all&spell_targets.shuriken_storm>=3&!talent.flagellation.enabled +CDsAPL:AddSpell( + ShurikenTornado:CastableIf( + function(self) + return self:IsKnownAndUsable() and ShadowDance:GetCooldownRemaining() == 0 and + not Player:IsStealthed() and + Player:GetEnemies(10) >= 3 and + not Flagellation:IsKnown() + end + ):SetTarget(Target) ) --- Use Thistle Tea with Shadow Dance. -AOEAPL:AddSpell( - ThistleTea:CastableIf(function(self) - return Target:Exists() and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetPowerDeficit() >= 100 and - ThistleTea:GetTimeSinceLastCast() >= 3 - end):SetTarget(Player) +-- actions.cds+=/shadow_dance,if=!buff.shadow_dance.up&fight_remains<=8+talent.subterfuge.enabled +CDsAPL:AddSpell( + ShadowDance:CastableIf( + function(self) + return Player:IsAffectingCombat() and self:IsKnownAndUsable() and + not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + Target:TimeToDie() <= 8 + (Subterfuge:IsKnown() and 1 or 0) + end + ):SetTarget(Target) ) --- Use Finishing moves with 5 or more combo points with the following priority: --- Cast Slice and Dice if it needs to be refreshed for maintenance or if it is not up. -AOEAPL:AddSpell( - SliceAndDice:CastableIf(function(self) - return Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 5) and - ( - not Player:GetAuras():FindMy(SliceAndDice):IsUp() or +-- actions.cds+=/thistle_tea,if=cooldown.symbols_of_death.remains>=3&!buff.thistle_tea.up&(energy.deficit>=100|cooldown.thistle_tea.charges_fractional>=2.75&buff.shadow_dance.up)|buff.shadow_dance.remains>=4&!buff.thistle_tea.up&spell_targets.shuriken_storm>=3|!buff.thistle_tea.up&fight_remains<=(6*cooldown.thistle_tea.charges) +CDsAPL:AddSpell( + ThistleTea:CastableIf( + function(self) + return Player:IsAffectingCombat() and self:IsKnownAndUsable() and + (SymbolsOfDeath:GetCooldownRemaining() >= 3 + and not Player:GetAuras():FindMy(ThistleTea):IsUp() and + (Player:GetPowerDeficit() >= 100 or + self:GetChargesFractional() >= 2.75 and + Player:GetAuras():FindMy(ShadowDance):IsUp()) or + (Player:GetAuras():FindMy(ShadowDance):GetRemainingTime() >= 4 and + not Player:GetAuras():FindMy(ThistleTea):IsUp() and + Player:GetEnemies(10) >= 3) or + (not Player:GetAuras():FindMy(ThistleTea):IsUp() and + Player:TimeToDie() <= (6 * self:GetCharges()))) + end + ):SetTarget(Player) +) + +-- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.symbols_of_death.up&(buff.shadow_blades.up|cooldown.shadow_blades.remains<=10) +-- actions.cds+=/blood_fury,if=buff.symbols_of_death.up +-- actions.cds+=/berserking,if=buff.symbols_of_death.up +-- actions.cds+=/fireblood,if=buff.symbols_of_death.up +-- actions.cds+=/ancestral_call,if=buff.symbols_of_death.up +-- actions.cds+=/use_item,name=manic_grieftorch,use_off_gcd=1,if=gcd.remains>gcd.max-0.1,if=!stealthed.all +-- # Default fallback for usable items: Use with Symbols of Death. +-- actions.cds+=/use_items,if=buff.symbols_of_death.up|fight_remains<20 +CDsAPL:AddAPL( + ItemsAPL, + function() + return Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() or (Target:IsBoss() and Target:TimeToDie() < 20) + end +) + +-- # Finishers While using Premeditation, avoid casting Slice and Dice when Shadow Dance is soon to be used, except for Kyrian +-- actions.finish=variable,name=premed_snd_condition,value=talent.premeditation.enabled&spell_targets.shuriken_storm<5 +FinishAPL:AddVariable( + 'premed_snd_condition', + function() + return Premeditation:IsKnown() and Player:GetEnemies(10) < 5 + end +) + +-- actions.finish+=/slice_and_dice,if=!variable.premed_snd_condition&spell_targets.shuriken_storm<6&!buff.shadow_dance.up&buff.slice_and_dice.remains= 5) and ( - not Target:GetAuras():FindMy(Rupture):IsUp() or +-- actions.finish+=/slice_and_dice,if=variable.premed_snd_condition&cooldown.shadow_dance.charges_fractional<1.75&buff.slice_and_dice.remains=2) +FinishAPL:AddVariable( + 'skip_rupture', + function() + return (Player:GetAuras():FindMy(ThistleTea):IsUp() and Player:GetEnemies(10) == 1) or + (Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetEnemies(10) == 1 or + Target:GetAuras():FindMy(Rupture):IsUp() and Player:GetEnemies(10) >= 2)) + end +) + +-- # Keep up Rupture if it is about to run out. +-- actions.finish+=/rupture,if=(!variable.skip_rupture|variable.priority_rotation)&target.time_to_die-remains>6&refreshable +FinishAPL:AddSpell( + Rupture:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and + (not DefaultAPL:GetVariable('skip_rupture') or + DefaultAPL:GetVariable('priority_rotation')) and + Target:TimeToDie() - Target:GetAuras():FindMy(Rupture):GetRemainingTime() > 6 and Target:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 - ) - and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - and not Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() - and not Player:GetAuras():FindMy(ThistleTea):IsUp() - end):SetTarget(Target) + end + ):SetTarget(Target) ) --- Cast Rupture on all targets. (scam??) -AOEAPL:AddSpell( - Rupture:CastableIf(function(self) - return RuptureTarget:Exists() and self:IsInRange(RuptureTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(RuptureTarget) >= 6) and ( - not RuptureTarget:GetAuras():FindMy(Rupture):IsUp() or - RuptureTarget:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 - ) - and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(RuptureTarget) +-- # Refresh Rupture early for Finality +-- actions.finish+=/rupture,if=!variable.skip_rupture&buff.finality_rupture.up&cooldown.shadow_dance.remains<12&cooldown.shadow_dance.charges_fractional<=1&spell_targets.shuriken_storm=1&(talent.dark_brew|talent.danse_macabre) +FinishAPL:AddSpell( + Rupture:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and not DefaultAPL:GetVariable('skip_rupture') and + Player:GetAuras():FindMy(FinalityRupture):IsUp() and + ShadowDance:GetCooldownRemaining() < 12 and + ShadowDance:GetChargesFractional() <= 1 and + Player:GetEnemies(10) == 1 and + (DarkBrew:IsKnown() or DanseMacabre:IsKnown()) + end + ):SetTarget(Target) +) + +-- # Sync Cold Blood with Secret Technique when possible +-- actions.finish+=/cold_blood,if=buff.shadow_dance.up&(buff.danse_macabre.stack>=3|!talent.danse_macabre)&cooldown.secret_technique.ready +FinishAPL:AddSpell( + ColdBlood:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or + not DanseMacabre:IsKnown()) and + SecretTechnique:OnCooldown() + end + ):SetTarget(Target) ) -- actions.finish+=/secret_technique,if=buff.shadow_dance.up&(buff.danse_macabre.stack>=3|!talent.danse_macabre)&(!talent.cold_blood|cooldown.cold_blood.remains>buff.shadow_dance.remains-2) -AOEAPL:AddSpell( - SecretTechnique:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 5) and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and - (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or - not DanseMacabre:IsKnown()) and - (not ColdBlood:IsKnown() or - ColdBlood:GetCooldownRemaining() > Player:GetAuras():FindMy(ShadowDanceAura):GetRemainingTime() - 2) - end):SetTarget(Target) +FinishAPL:AddSpell( + SecretTechnique:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and + Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and + (Player:GetAuras():FindMy(DanseMacabre):GetCount() >= 3 or + not DanseMacabre:IsKnown()) and + (not ColdBlood:IsKnown() or + ColdBlood:GetCooldownRemaining() > Player:GetAuras():FindMy(ShadowDanceAura):GetRemainingTime() - 2) + end + ):SetTarget(Target) ) --- Cast Black Powder with 3 or more targets, 2 or more when talented into Dark Brew. -AOEAPL:AddSpell( - BlackPowder:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - (Player:GetComboPoints(Target) >= 5) and - (Player:GetEnemies(10) >= 3 or - (Player:GetEnemies(10) >= 2 and - DarkBrew:IsKnown())) - end):SetTarget(Target) +-- # Multidotting targets that will live for the duration of Rupture, refresh during pandemic. +-- actions.finish+=/rupture,cycle_targets=1,if=!variable.skip_rupture&!variable.priority_rotation&spell_targets.shuriken_storm>=2&target.time_to_die>=(2*combo_points)&refreshable +FinishAPL:AddSpell( + Rupture:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(RuptureTarget) and + not DefaultAPL:GetVariable('skip_rupture') and + not DefaultAPL:GetVariable('priority_rotation') and + Player:GetEnemies(10) >= 2 and + RuptureTarget:TimeToDie() >= (2 * Player:GetComboPoints()) and + RuptureTarget:GetAuras():FindMy(Rupture):GetRemainingTime() < 6 + end + ):SetTarget(RuptureTarget) ) --- Cast Eviscerate. -AOEAPL:AddSpell( - Eviscerate:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetComboPoints(Target) >= 5 - end):SetTarget(Target) + +-- # Refresh Rupture early if it will expire during Symbols. Do that refresh if SoD gets ready in the next 5s. +-- actions.finish+=/rupture,if=!variable.skip_rupture&remainscooldown.symbols_of_death.remains+5 +FinishAPL:AddSpell( + Rupture:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and not DefaultAPL:GetVariable('skip_rupture') and + Target:GetAuras():FindMy(Rupture):GetRemainingTime() < + SymbolsOfDeath:GetCooldownRemaining() + 10 and + SymbolsOfDeath:GetCooldownRemaining() <= 5 and + Target:TimeToDie() - Target:GetAuras():FindMy(Rupture):GetRemainingTime() > + SymbolsOfDeath:GetCooldownRemaining() + 5 + end + ):SetTarget(Target) ) --- Vanish - Is a fairly weak cooldown. It is best to use on low combo points for a Shadowstrike cast. Use it after Secret Technique in Shadow Dance when playing with Danse Macabre. -AOEAPL:AddSpell( - Vanish:CastableIf(function(self) - return Tank:Exists() and Target:Exists() and Player:InMelee(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetComboPoints(Target) < 4 - end):SetTarget(Player) +-- actions.finish+=/black_powder,if=!variable.priority_rotation&spell_targets>=3 +FinishAPL:AddSpell( + BlackPowder:CastableIf( + function(self) + return self:IsKnownAndUsable() and not DefaultAPL:GetVariable('priority_rotation') and + Player:GetEnemies(10) >= 3 + end + ):SetTarget(Target) ) --- Use Combo Point builder with the following priority: --- Use Shuriken Storm on 2 targets outside of Shadow Dance. -AOEAPL:AddSpell( - ShurikenStorm:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetEnemies(10) == 2 and - not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(Player) +-- actions.finish+=/eviscerate +FinishAPL:AddSpell( + Eviscerate:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and true + end + ):SetTarget(Target) ) --- Use Shadowstrike on 2 and 3 targets during Shadow Dance or to proc Premeditation. -AOEAPL:AddSpell( - Shadowstrike:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetEnemies(10) >= 2 and Player:GetEnemies(10) <= 3 and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp() - end):SetTarget(Target) +-- # Stealth Cooldowns Helper Variable +-- actions.stealth_cds=variable,name=shd_threshold,value=cooldown.shadow_dance.charges_fractional>=0.75+talent.shadow_dance +StealthCDsAPL:AddVariable( + 'shd_threshold', + function(self) + return ShadowDance:GetChargesFractional() >= 0.75 + (ShadowDance:IsKnown() and 1 or 0) + end ) --- Use Shuriken Storm at > 2 targets. -AOEAPL:AddSpell( - ShurikenStorm:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetEnemies(10) > 2 - end):SetTarget(Player) +-- # Vanish if we are capping on Dance charges. Early before first dance if we have no Nightstalker but Dark Shadow in order to get Rupture up (no Master Assassin). +-- actions.stealth_cds+=/vanish,if=(!talent.danse_macabre|spell_targets.shuriken_storm>=3)&!variable.shd_threshold&combo_points.deficit>1 +StealthCDsAPL:AddSpell( + Vanish:CastableIf( + function(self) + return Player:IsAffectingCombat() and not Player:GetAuras():FindMy(Stealth):IsUp() and + self:IsKnownAndUsable() and + (not DanseMacabre:IsKnown() or Player:GetEnemies(10) >= 3) and + not StealthCDsAPL:GetVariable('shd_threshold') and + Player:GetComboPointsDeficit() > 1 + end + ):SetTarget(Target) ) --- Use gloomblade at <= 2 targets. -AOEAPL:AddSpell( - Gloomblade:CastableIf(function(self) - return Target:Exists() and self:IsInRange(Target) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetEnemies(10) <= 2 - end):SetTarget(Target) +-- # Pool for Shadowmeld + Shadowstrike unless we are about to cap on Dance charges. Only when Find Weakness is about to run out. +-- actions.stealth_cds+=/pool_resource,for_next=1,extra_amount=40,if=race.night_elf + + +-- actions.stealth_cds+=/shadowmeld,if=energy>=40&energy.deficit>=10&!variable.shd_threshold&combo_points.deficit>4 +StealthCDsAPL:AddSpell( + Shadowmeld:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetEnergy() >= 40 and + Player:GetPowerDeficit() >= 10 and + not StealthCDsAPL:GetVariable('shd_threshold') and + Player:GetComboPointsDeficit() > 4 + end + ):SetTarget(Target) ) -SubModulue:Sync(function() - SpecialAPL:Execute() - if Player:GetEnemies(10) >= 2 then - AOEAPL:Execute() - else - DefaultAPL:Execute() +-- # CP thresholds for entering Shadow Dance Default to start dance with 0 or 1 combo point +-- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points<=1 +StealthCDsAPL:AddVariable( + 'shd_combo_points', + function(self) + return Player:GetComboPoints() <= 1 + end +) + +-- # Use stealth cooldowns with high combo points when playing shuriken tornado or with high target counts +-- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points.deficit<=1,if=spell_targets.shuriken_storm>(4-2*talent.shuriken_tornado.enabled)|variable.priority_rotation&spell_targets.shuriken_storm>=4 +StealthCDsAPL:AddVariable( + 'shd_combo_points', + function(self) + return Player:GetComboPointsDeficit() <= 1 and + ((Player:GetEnemies(10) > (4 - 2 * (ShurikenTornado:IsKnown() and 1 or 0))) or + (DefaultAPL:GetVariable('priority_rotation') and + Player:GetEnemies(10) >= 4)) + end +) + +-- # Use stealth cooldowns on any combo point on 4 targets +-- actions.stealth_cds+=/variable,name=shd_combo_points,value=1,if=spell_targets.shuriken_storm=(4-talent.seal_fate) +StealthCDsAPL:AddVariable( + 'shd_combo_points', + function(self) + return Player:GetEnemies(10) == (4 - (SealFate:IsKnown() and 1 or 0)) + end +) + +-- # Dance during Symbols or above threshold. +-- actions.stealth_cds+=/shadow_dance,if=(variable.shd_combo_points&(buff.symbols_of_death.remains>=(2.2-talent.flagellation.enabled)|variable.shd_threshold)|buff.flagellation.up|buff.flagellation_persist.remains>=6|spell_targets.shuriken_storm>=4&cooldown.symbols_of_death.remains>10)&!buff.the_rotten.up +StealthCDsAPL:AddSpell( + ShadowDance:CastableIf( + function(self) + return Player:IsAffectingCombat() and self:IsKnownAndUsable() and + ((StealthCDsAPL:GetVariable('shd_combo_points') and + (Player:GetAuras():FindMy(SymbolsOfDeath):GetRemainingTime() >= + (2.2 - (Flagellation:IsKnown() and 1 or 0)) or + StealthCDsAPL:GetVariable('shd_threshold'))) or + Player:GetAuras():FindMy(Flagellation):IsUp() or + Player:GetAuras():FindMy(FlagellationPersist):GetRemainingTime() >= 6 or + Player:GetEnemies(10) >= 4 and + SymbolsOfDeath:GetCooldownRemaining() > 10) and + not Target:GetAuras():FindMy(TheRotten):IsUp() + end + ):SetTarget(Target) +) + +-- # Burn Dances charges if before the fight ends if SoD won't be ready in time. +-- actions.stealth_cds+=/shadow_dance,if=variable.shd_combo_points&fight_remains=cp_max_spend +StealthedAPL:AddAPL( + FinishAPL, + function(self) + return DefaultAPL:GetVariable('effective_combo_points') >= Player:GetComboPointsMax() + end +) + +-- # Finish earlier with Shuriken tornado up. +-- actions.stealthed+=/call_action_list,name=finish,if=buff.shuriken_tornado.up&combo_points.deficit<=2 +StealthedAPL:AddAPL( + FinishAPL, + function(self) + return Player:GetAuras():FindMy(ShurikenTornado):IsUp() and + Player:GetComboPointsDeficit() <= 2 + end +) + +-- # Also safe to finish at 4+ CP with exactly 4 targets. (Same as outside stealth.) +-- actions.stealthed+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=4-talent.seal_fate&variable.effective_combo_points>=4 +StealthedAPL:AddAPL( + FinishAPL, + function() + return Player:GetEnemies(10) >= 4 - ((SealFate:IsKnown() and + DefaultAPL:GetVariable('effective_combo_points') >= 4) and 1 or 0) + end +) + +-- # Finish at lower combo points if you are talented in DS, SS or Seal Fate +-- actions.stealthed+=/call_action_list,name=finish,if=combo_points.deficit<=1+(talent.seal_fate|talent.deeper_stratagem|talent.secret_stratagem) +StealthedAPL:AddAPL( + FinishAPL, + function() + return Player:GetComboPointsDeficit() <= + 1 + ((SealFate:IsKnown() or DeeperStratagem:IsKnown() or SecretStratagem:IsKnown()) and 1 or 0) + end +) + +-- # Use Gloomblade or Backstab when close to hitting max PV stacks +-- actions.stealthed+=/gloomblade,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 +StealthedAPL:AddSpell( + Gloomblade:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and + Player:GetAuras():FindMy(PerforatedVeins):GetCount() >= 5 and + Player:GetEnemies(10) < 3 + end + ):SetTarget(Target) +) + +-- actions.stealthed+=/backstab,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 +StealthedAPL:AddSpell( + Backstab:CastableIf( + function(self) + return self:IsKnownAndUsable() and self:IsInRange(Target) and + Player:GetAuras():FindMy(PerforatedVeins):GetCount() >= 5 and + Player:GetEnemies(10) < 3 + end + ):SetTarget(Target) +) + +-- actions.stealthed+=/shadowstrike,if=stealthed.sepsis&spell_targets.shuriken_storm<4 +StealthedAPL:AddSpell( + Shadowstrike:CastableIf( + function(self) + return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Sepsis):IsUp() and + Player:GetEnemies(10) < 4 + end + ):SetTarget(Target) +) + +-- actions.stealthed+=/shuriken_storm,if=spell_targets>=3+buff.the_rotten.up&(!buff.premeditation.up|spell_targets>=7) +StealthedAPL:AddSpell( + ShurikenStorm:CastableIf( + function(self) + return self:IsKnownAndUsable() and + Player:GetEnemies(10) >= 3 + ((Player:GetAuras():FindMy(TheRotten):IsUp() and + (not Player:GetAuras():FindMy(Premeditation):IsUp() or Player:GetEnemies(10) >= 7)) and 1 or 0) + end + ):SetTarget(Target) +) + +-- # Shadowstrike to refresh Find Weakness and to ensure we can carry over a full FW into the next SoD if possible. +-- actions.stealthed+=/shadowstrike,if=debuff.find_weakness.remains<=1|cooldown.symbols_of_death.remains<18&debuff.find_weakness.remains 0 +end + -- Cast the spell function Spell:Cast(unit, condition) if condition and not self:EvaluateCondition(condition) then diff --git a/src/Unit/Unit.lua b/src/Unit/Unit.lua index 211b639..138d5b9 100644 --- a/src/Unit/Unit.lua +++ b/src/Unit/Unit.lua @@ -5,6 +5,10 @@ local Unit = { cache = nil, aura_table = nil, unit = nil, + last_shadow_techniques = 0, + swings_since_sht = 0, + last_off_attack = 0, + last_main_attack = 0, } function Unit:__index(k) @@ -428,6 +432,15 @@ function Unit:GetComboPoints() return UnitPower(self.unit, 4) end +function Unit:GetComboPointsMax() + return UnitPowerMax(self.unit, 4) +end + +-- Get combopoints deficit +function Unit:GetComboPointsDeficit() + return self:GetComboPointsMax() - self:GetComboPoints() +end + -- IsUnit function Unit:IsUnit(unit) return UnitIsUnit(self.unit, unit.unit) @@ -618,4 +631,201 @@ function Unit:GetCombatTime() return GetTime() - self.last_combat_time end +-- Get units gcd time +function Unit:GetGCD() + local start, duration = GetSpellCooldown(61304) + if start == 0 then + return 0 + end + + return duration - (GetTime() - start) +end + +-- Get units max gcd time +--[[ + The GCD without Haste is 1.5 seconds +With 50% Haste the GCD is 1 second +With 100% Haste the GCD is 0.5 seconds +The GCD won't drop below 1 second +More than 50% Haste will drop a spell below 1 second + +]] +function Unit:GetMaxGCD() + local haste = UnitSpellHaste(self.unit) + if haste > 50 then + haste = 50 + end + + return 1.5 / (1 + haste / 100) +end + +-- IsStealthed +function Unit:IsStealthed() + local Stealth = Bastion.SpellBook:GetSpell(1784) + local Vanish = Bastion.SpellBook:GetSpell(1856) + local ShadowDance = Bastion.SpellBook:GetSpell(185422) + local Subterfuge = Bastion.SpellBook:GetSpell(115192) + local Shadowmeld = Bastion.SpellBook:GetSpell(58984) + local Sepsis = Bastion.SpellBook:GetSpell(328305) + + + return self:GetAuras():FindAny(Stealth) or self:GetAuras():FindAny(ShadowDance) +end + +-- Get unit swing timers +function Unit:GetSwingTimers() + local main_speed, off_speed = UnitAttackSpeed(self.unit) + local main_speed = main_speed or 2 + local off_speed = off_speed or 2 + + local main_speed_remains = main_speed - (GetTime() - self.last_main_attack) + local off_speed_remains = off_speed - (GetTime() - self.last_off_attack) + + if main_speed_remains < 0 then + main_speed_remains = 0 + end + + if off_speed_remains < 0 then + off_speed_remains = 0 + end + + return main_speed_remains, off_speed_remains +end + +function Unit:WatchForSwings() + Bastion.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() + local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = CombatLogGetCurrentEventInfo() + + if sourceGUID == self:GetGUID() then + if subtype == "SPELL_ENERGIZE" and spellID == 196911 then + self.last_shadow_techniques = GetTime() + self.swings_since_sht = 0 + end + + if subtype:sub(1, 5) == "SWING" and not multistrike then + if subtype == "SWING_MISSED" then + offhand = spellName + end + + local now = GetTime() + + if now > self.last_shadow_techniques + 3 then + self.swings_since_sht = self.swings_since_sht + 1 + end + + if offhand then + self.last_off_attack = GetTime() + else + self.last_main_attack = GetTime() + end + end + end + end) +end + +-- GetTimeToShurikenTornado +--[[ + spec:RegisterStateTable( "time_to_sht", setmetatable( {}, { + __index = function( t, k ) + local n = tonumber( k ) + n = n - ( n % 1 ) + + if not n or n > 5 then return 3600 end + + if n <= swings_since_sht then return 0 end + + local mh_speed = swings.mainhand_speed + local mh_next = ( swings.mainhand > now - 3 ) and ( swings.mainhand + mh_speed ) or now + ( mh_speed * 0.5 ) + + local oh_speed = swings.offhand_speed + local oh_next = ( swings.offhand > now - 3 ) and ( swings.offhand + oh_speed ) or now + + table.wipe( sht ) + + if mh_speed and mh_speed > 0 then + for i = 1, 4 do + insert( sht, mh_next + ( i * mh_speed ) ) + end + end + + if oh_speed and oh_speed > 0 then + for i = 1, 4 do + insert( sht, oh_next + ( i * oh_speed ) ) + end + end + + local i = 1 + + while( sht[i] ) do + if sht[i] < last_shadow_techniques + 3 then + table.remove( sht, i ) + else + i = i + 1 + end + end + + if #sht > 0 and n - swings_since_sht < #sht then + table.sort( sht ) + return max( 0, sht[ n - swings_since_sht ] - query_time ) + else + return 3600 + end + end +} ) ) +]] +function Unit:GetTimeToShurikenTornado(n) + local now = GetTime() + local sht = {} + local swings = self:GetSwingTimers() + + if not self.swings_since_sht then + self.swings_since_sht = 0 + end + + if not self.last_shadow_techniques then + self.last_shadow_techniques = 0 + end + + if n <= self.swings_since_sht then + return 0 + end + + local mh_speed = swings[1] + local mh_next = (self.last_mh > now - 3) and (self.last_mh + mh_speed) or now + (mh_speed * 0.5) + + local oh_speed = swings[2] + local oh_next = (self.last_oh > now - 3) and (self.last_oh + oh_speed) or now + + table.wipe(sht) + + if mh_speed and mh_speed > 0 then + for i = 1, 4 do + table.insert(sht, mh_next + (i * mh_speed)) + end + end + + if oh_speed and oh_speed > 0 then + for i = 1, 4 do + table.insert(sht, oh_next + (i * oh_speed)) + end + end + + local i = 1 + + while (sht[i]) do + if sht[i] < self.last_shadow_techniques + 3 then + table.remove(sht, i) + else + i = i + 1 + end + end + + if #sht > 0 and n - self.swings_since_sht < #sht then + table.sort(sht) + return math.max(0, sht[n - self.swings_since_sht] - now) + else + return 3600 + end +end + return Unit