From 1400e3f4b4ef9365877649c012662de95c8ee4c2 Mon Sep 17 00:00:00 2001 From: 4n0n <4n0n@tinkr.site> Date: Fri, 10 Feb 2023 23:07:27 -0600 Subject: [PATCH 1/3] bug fixes --- src/MythicPlusUtils/MythicPlusUtils.lua | 145 ++++++++++++++---------- src/ObjectManager/ObjectManager.lua | 4 +- src/Spell/Spell.lua | 24 ++-- src/Unit/Unit.lua | 37 +++--- 4 files changed, 129 insertions(+), 81 deletions(-) diff --git a/src/MythicPlusUtils/MythicPlusUtils.lua b/src/MythicPlusUtils/MythicPlusUtils.lua index 6384383..7e0ee3c 100644 --- a/src/MythicPlusUtils/MythicPlusUtils.lua +++ b/src/MythicPlusUtils/MythicPlusUtils.lua @@ -19,70 +19,78 @@ function MythicPlusUtils:New() self.random = math.random(1000000, 9999999) self.kickList = { -- Algeth'ar Academy - [396812] = true, -- https://www.wowhead.com/spell=396812/mystic-blast - [388392] = true, -- https://www.wowhead.com/spell=388392/monotonous-lecture - [388863] = true, -- https://www.wowhead.com/spell=388863/mana-void - [388862] = true, -- https://www.wowhead.com/spell=388862/surge - [377389] = true, -- https://www.wowhead.com/spell=377389/call-of-the-flock - [388623] = true, -- https://www.wowhead.com/spell=388623/branch-out - [396640] = true, -- https://www.wowhead.com/spell=396640/healing-touch - [387975] = true, -- https://www.wowhead.com/spell=387975/arcane-missiles - [387843] = true, -- https://www.wowhead.com/spell=387843/astral-bomb - + [396812] = { true, true }, -- https://www.wowhead.com/spell=396812/mystic-blast + [388392] = { true, true }, -- https://www.wowhead.com/spell=388392/monotonous-lecture + [388863] = { true, true }, -- https://www.wowhead.com/spell=388863/mana-void + [388862] = { true, true }, -- https://www.wowhead.com/spell=388862/surge + [377389] = { true, true }, -- https://www.wowhead.com/spell=377389/call-of-the-flock + [388623] = { true, true }, -- https://www.wowhead.com/spell=388623/branch-out + [396640] = { true, true }, -- https://www.wowhead.com/spell=396640/healing-touch + [387975] = { true, true }, -- https://www.wowhead.com/spell=387975/arcane-missiles + [387843] = { true, true }, -- https://www.wowhead.com/spell=387843/astral-bomb -- Court of Stars - [211401] = true, -- https://wowhead.com/spell=211401 - [207980] = true, -- https://wowhead.com/spell=207980 - [208165] = true, -- https://wowhead.com/spell=208165 - [207881] = true, -- https://wowhead.com/spell=207881 - [209413] = true, -- https://wowhead.com/spell=209413 - + [211401] = { true, true }, -- https://www.wowhead.com/spell=211401/drifting-embers + [207980] = { true, true }, -- https://www.wowhead.com/spell=207980/disintegration-beam + [208165] = { true, true }, -- https://www.wowhead.com/spell=208165/withering-soul + [207881] = { true, true }, -- https://www.wowhead.com/spell=207881/infernal-eruption + [209413] = { true, false }, -- https://www.wowhead.com/spell=209413/suppress + [209485] = { true, true }, -- https://www.wowhead.com/spell=209485/drain-magic + [209410] = { true, true }, -- https://www.wowhead.com/spell=209410/nightfall-orb + [211470] = { true, true }, -- https://www.wowhead.com/spell=211470/bewitch + [225100] = { true, false }, -- https://www.wowhead.com/spell=225100/charging-station + [211299] = { true, false }, -- https://www.wowhead.com/spell=211299/searing-glare -- Halls of Valor - [198595] = true, -- https://wowhead.com/spell=198595 - [198959] = true, -- https://wowhead.com/spell=198959 - [215433] = true, -- https://wowhead.com/spell=215433 - [192288] = true, -- https://wowhead.com/spell=192288 - [199726] = true, -- https://wowhead.com/spell=199726 - [198750] = true, -- https://wowhead.com/spell=198750 - + [198595] = { true, true }, -- https://www.wowhead.com/spell=198595/thunderous-bolt + [198959] = { true, true }, -- https://www.wowhead.com/spell=198959/etch + [215433] = { true, true }, -- https://www.wowhead.com/spell=215433/holy-radiance + [192288] = { true, true }, -- https://www.wowhead.com/spell=192288/searing-light + [199726] = { true, true }, -- https://www.wowhead.com/spell=199726/unruly-yell + [198750] = { true, true }, -- https://www.wowhead.com/spell=198750/surge + [198934] = { true, true }, -- https://www.wowhead.com/spell=198934/rune-of-healing + [192563] = { true, true }, -- https://www.wowhead.com/spell=192563/cleansing-flames -- Ruby Life Pools - [372749] = true, -- https://wowhead.com/spell=372749 - [373803] = true, -- https://wowhead.com/spell=373803 - [373017] = true, -- https://wowhead.com/spell=373017 - [392398] = true, -- https://wowhead.com/spell=392398 - [392451] = true, -- https://wowhead.com/spell=392451 - [385310] = true, -- https://wowhead.com/spell=385310 - + [372749] = { true, true }, -- https://www.wowhead.com/spell=372749/ice-shield + -- [373803] = { true, true }, -- https://www.wowhead.com/spell=373803/cold-claws + [373017] = { true, true }, -- https://www.wowhead.com/spell=373017/roaring-blaze + [392398] = { true, true }, -- https://www.wowhead.com/spell=392398/crackling-detonation + [392451] = { true, true }, -- https://www.wowhead.com/spell=392451/flashfire + [385310] = { true, true }, -- https://www.wowhead.com/spell=385310/lightning-bolt + [384194] = { true, true }, -- https://www.wowhead.com/spell=384194/cinderbolt + [373680] = { true, false }, -- https://www.wowhead.com/spell=373680/frost-overload -- Shadowmoon Burial Grounds - [152818] = true, -- https://wowhead.com/spell=152818 - [156776] = true, -- https://wowhead.com/spell=156776 - [156722] = true, -- https://wowhead.com/spell=156722 - [398206] = true, -- https://wowhead.com/spell=398206 - [153524] = true, -- https://wowhead.com/spell=153524 - [156718] = true, -- https://wowhead.com/spell=156718 - + [152818] = { true, true }, -- https://www.wowhead.com/spell=152818/shadow-mend + [156776] = { true, true }, -- https://www.wowhead.com/spell=156776/rending-voidlash + [156722] = { true, true }, -- https://www.wowhead.com/spell=156722/void-bolt + [398206] = { true, true }, -- https://www.wowhead.com/spell=398206/death-blast + [153524] = { true, true }, -- https://www.wowhead.com/spell=153524/plague-spit + [156718] = { true, true }, -- https://www.wowhead.com/spell=156718/necrotic-burst -- Temple of the Jade Serpent - [397888] = true, -- https://wowhead.com/spell=397888 - [395859] = true, -- https://wowhead.com/spell=395859 - [396073] = true, -- https://wowhead.com/spell=396073 - [397914] = true, -- https://wowhead.com/spell=397914 - + [397888] = { true, false }, -- https://www.wowhead.com/spell=397888/hydrolance + [395859] = { true, true }, -- https://www.wowhead.com/spell=395859/haunting-scream + [396073] = { true, true }, -- https://www.wowhead.com/spell=396073/cat-nap + [397914] = { true, true }, -- https://www.wowhead.com/spell=397914/defiling-mist + [397899] = { false, true }, -- https://www.wowhead.com/spell=397899/leg-sweep + [397801] = { true, false }, -- https://www.wowhead.com/spell=397801/hydrolance + [395872] = { true, true }, -- https://www.wowhead.com/spell=395872/sleepy-soliloquy -- The Azure Vault - [375602] = true, -- https://wowhead.com/spell=375602 - [387564] = true, -- https://wowhead.com/spell=387564 - [373932] = true, -- https://wowhead.com/spell=373932 - [386546] = true, -- https://wowhead.com/spell=386546 - [389804] = true, -- https://wowhead.com/spell=389804 - [377488] = true, -- https://wowhead.com/spell=377488 - [377503] = true, -- https://wowhead.com/spell=377503 - + [375602] = { true, true }, -- https://www.wowhead.com/spell=375602/erratic-growth + [387564] = { true, true }, -- https://www.wowhead.com/spell=387564/mystic-vapors + [373932] = { true, true }, -- https://www.wowhead.com/spell=373932/illusionary-bolt + [386546] = { true, true }, -- https://www.wowhead.com/spell=386546/waking-bane + [389804] = { true, true }, -- https://www.wowhead.com/spell=389804/heavy-tome + [377488] = { true, true }, -- https://www.wowhead.com/spell=377488/icy-bindings + [377503] = { true, true }, -- https://www.wowhead.com/spell=377503/condensed-frost + [375596] = { true, true }, -- https://www.wowhead.com/spell=375596/erratic-growth -- NO - [384365] = true, -- https://wowhead.com/spell=384365 - [386012] = true, -- https://wowhead.com/spell=386012 - [386024] = true, -- https://wowhead.com/spell=386024 - [387411] = true, -- https://wowhead.com/spell=387411 - [387606] = true, -- https://wowhead.com/spell=387606 - [373395] = true, -- https://wowhead.com/spell=373395 - [376725] = true, -- https://wowhead.com/spell=376725 + [384365] = { true, true }, -- https://www.wowhead.com/spell=384365/disruptive-shout + [386012] = { true, false }, -- https://www.wowhead.com/spell=386012/stormbolt + [386024] = { true, true }, -- https://www.wowhead.com/spell=386024/tempest + [387411] = { true, true }, -- https://www.wowhead.com/spell=387411/death-bolt-volley + [387606] = { true, true }, -- https://www.wowhead.com/spell=387606/dominate + [373395] = { true, true }, -- https://www.wowhead.com/spell=373395/bloodcurdling-shout + [376725] = { true, true }, -- https://www.wowhead.com/spell=376725/storm-bolt + [384808] = { true, true }, -- https://www.wowhead.com/spell=384808/guardian-wind + -- [363607] = {true, true}, -- https://www.wowhead.com/spell=363607/domination-bolt } Bastion.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras) @@ -101,7 +109,7 @@ function MythicPlusUtils:New() WriteFile('bastion-MPlusDebuffs-' .. self.random .. '.lua', [[ AuraName: ]] .. aura:GetName() .. [[ AuraID: ]] .. aura:GetSpell():GetID() .. "\n" .. [[ - ]] , true) + ]], true) end end end @@ -167,7 +175,26 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent) if castingSpell then local spellID = castingSpell:GetID() - if self.kickList[spellID] and unit:IsInterruptibleAt(percent) then + local isKick = unpack(self.kickList[spellID] or { false, false }) + + if isKick and unit:IsInterruptibleAt(percent) then + return true + end + end + + return false +end + +---@param unit Unit +---@param percent number +---@return boolean +function MythicPlusUtils:CastingCriticalStun(unit, percent) + local castingSpell = unit:GetCastingOrChannelingSpell() + + if castingSpell then + local spellID = castingSpell:GetID() + local _, isStun = unpack(self.kickList[spellID] or { false, false }) + if isStun and unit:IsInterruptibleAt(percent, true) then return true end end diff --git a/src/ObjectManager/ObjectManager.lua b/src/ObjectManager/ObjectManager.lua index 947be6b..1aaca3d 100644 --- a/src/ObjectManager/ObjectManager.lua +++ b/src/ObjectManager/ObjectManager.lua @@ -75,7 +75,7 @@ function ObjectManager:Refresh() for _, object in pairs(objects) do self:EnumLists(object) - if ObjectType(object) == 5 or ObjectType(object) == 6 then + if ({ [5] = true,[6] = true,[7] = true })[ObjectType(object)] then local unit = Bastion.UnitManager:GetObject(ObjectGUID(object)) if not unit then unit = Bastion.Unit:New(object) @@ -84,7 +84,7 @@ function ObjectManager:Refresh() if unit:GetID() == 120651 then self.explosives:push(unit) - elseif unit:IsPlayer() and unit:IsInParty() then + elseif unit:IsPlayer() and (unit:IsInParty() or unit == Bastion.UnitManager['player']) then self.friends:push(unit) elseif unit:IsEnemy() then self.enemies:push(unit) diff --git a/src/Spell/Spell.lua b/src/Spell/Spell.lua index 6d4bdc5..aed4458 100644 --- a/src/Spell/Spell.lua +++ b/src/Spell/Spell.lua @@ -7,6 +7,7 @@ local Spell = { PreCastFunc = false, OnCastFunc = false, PostCastFunc = false, + lastCastAttempt = 0, wasLooking = false, lastCastAt = 0, conditions = {}, @@ -305,6 +306,15 @@ function Spell:GetTimeSinceLastCast() return GetTime() - self:GetLastCastTime() end +-- Get the time since the last cast attempt +---@return number +function Spell:GetTimeSinceLastCastAttempt() + if not self.lastCastAttempt then + return math.huge + end + return GetTime() - self.lastCastAttempt +end + -- Get the spells charges ---@return number function Spell:GetCharges() @@ -404,24 +414,24 @@ end ---@return boolean function Spell:IsMagicDispel() return ({ - [88423] = true - })[self:GetID()] + [88423] = true + })[self:GetID()] end -- IsCurseDispel ---@return boolean function Spell:IsCurseDispel() return ({ - [88423] = true - })[self:GetID()] + [88423] = true + })[self:GetID()] end -- IsPoisonDispel ---@return boolean function Spell:IsPoisonDispel() return ({ - [88423] = true - })[self:GetID()] + [88423] = true + })[self:GetID()] end -- IsDiseaseDispel @@ -429,7 +439,7 @@ end function Spell:IsDiseaseDispel() return ({ - })[self:GetID()] + })[self:GetID()] end -- IsSpell diff --git a/src/Unit/Unit.lua b/src/Unit/Unit.lua index 256c078..5e1f549 100644 --- a/src/Unit/Unit.lua +++ b/src/Unit/Unit.lua @@ -372,8 +372,9 @@ end -- Get Casting or channeling spell ---@return Spell | nil function Unit:GetCastingOrChannelingSpell() - local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(self - .unit) + local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( + self + .unit) if not name then name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit @@ -408,8 +409,9 @@ end ---@return number function Unit:GetChannelOrCastPercentComplete() - local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(self - .unit) + local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( + self + .unit) if not name then name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit @@ -429,8 +431,9 @@ end -- Check if unit is interruptible ---@return boolean function Unit:IsInterruptible() - local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(self - .unit) + local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( + self + .unit) if not name then name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit @@ -447,8 +450,8 @@ end -- Check if unit is interruptible ---@param percent number ---@return boolean -function Unit:IsInterruptibleAt(percent) - if not self:IsInterruptible() then +function Unit:IsInterruptibleAt(percent, ignoreInterruptible) + if not ignoreInterruptible and not self:IsInterruptible() then return false end @@ -575,7 +578,7 @@ end ---@return boolean function Unit:IsTanking(unit) local isTanking, status, threatpct, rawthreatpct, threatvalue = UnitDetailedThreatSituation(self:GetOMToken(), - unit:GetOMToken()) + unit:GetOMToken()) return isTanking end @@ -642,15 +645,22 @@ end ---@param unit Unit ---@return boolean function Unit:InMelee(unit) - local x, y, z = ObjectPosition(self:GetOMToken()) - local x2, y2, z2 = ObjectPosition(unit:GetOMToken()) + local x, y, z = ObjectPosition(self.unit) + local x2, y2, z2 = ObjectPosition(unit.unit) if not x or not x2 then return false end + local scr = ObjectCombatReach(self.unit) + local ucr = ObjectCombatReach(unit.unit) + + if not scr or not ucr then + return false + end + local dist = math.sqrt((x - x2) ^ 2 + (y - y2) ^ 2 + (z - z2) ^ 2) - local maxDist = math.max((ObjectCombatReach(self:GetOMToken()) + 1.3333) + ObjectCombatReach(unit:GetOMToken()), 5.0) + local maxDist = math.max((scr + 1.3333) + ucr, 5.0) maxDist = maxDist + 1.0 + self:GetMeleeBoost() return dist <= maxDist @@ -851,7 +861,8 @@ end ---@return nil 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() + 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 From d5257bbf8f45b2960a1e496aa9a7b6892a192ba8 Mon Sep 17 00:00:00 2001 From: 4n0n <4n0n@tinkr.site> Date: Wed, 15 Feb 2023 18:01:34 -0600 Subject: [PATCH 2/3] Changes --- scripts/outlaw.lua | 1908 +++++++------- scripts/restodruid.lua | 559 ++-- scripts/subtlety.lua | 3211 ++++++++--------------- src/APL/APL.lua | 40 +- src/MythicPlusUtils/MythicPlusUtils.lua | 463 +++- src/Sequencer/Sequencer.lua | 87 + src/Spell/Spell.lua | 53 +- src/Unit/Unit.lua | 61 +- src/_bastion.lua | 64 +- 9 files changed, 3039 insertions(+), 3407 deletions(-) create mode 100644 src/Sequencer/Sequencer.lua diff --git a/scripts/outlaw.lua b/scripts/outlaw.lua index dd8b41a..73d2829 100644 --- a/scripts/outlaw.lua +++ b/scripts/outlaw.lua @@ -1,954 +1,954 @@ -local Tinkr, Bastion = ... - -local OutlawModule = Bastion.Module:New('outlaw') -local Evaluator = Tinkr.Util.Evaluator -local Player = Bastion.UnitManager:Get('player') -local None = Bastion.UnitManager:Get('none') -local Target = Bastion.UnitManager:Get('target') - -local Stealth = Bastion.SpellBook:GetSpell(115191) -local Kick = Bastion.SpellBook:GetSpell(1766) -local CountTheOdds = Bastion.SpellBook:GetSpell(381982) -local Shadowmeld = Bastion.SpellBook:GetSpell(58984) -local ShadowDance = Bastion.SpellBook:GetSpell(185313) -local ShadowDanceAura = Bastion.SpellBook:GetSpell(185422) -local HiddenOpportunity = Bastion.SpellBook:GetSpell(383281) -local RollTheBones = Bastion.SpellBook:GetSpell(315508) -local FanTheHammer = Bastion.SpellBook:GetSpell(381846) -local ImprovedAmbush = Bastion.SpellBook:GetSpell(381620) -local SummarilyDispatched = Bastion.SpellBook:GetSpell(381990) -local BladeFlurry = Bastion.SpellBook:GetSpell(13877) -local KillingSpree = Bastion.SpellBook:GetSpell(51690) -local ArcaneTorrent = Bastion.SpellBook:GetSpell(25046) -local ArcanePulse = Bastion.SpellBook:GetSpell(260364) -local LightsJudgment = Bastion.SpellBook:GetSpell(255647) -local BagOfTricks = Bastion.SpellBook:GetSpell(312411) -local Sepsis = Bastion.SpellBook:GetSpell(385408) -local BetweenTheEyes = Bastion.SpellBook:GetSpell(315341) -local GhostlyStrike = Bastion.SpellBook:GetSpell(196937) -local Dreadblades = Bastion.SpellBook:GetSpell(343142) -local Subterfuge = Bastion.SpellBook:GetSpell(108208) -local EchoingReprimand = Bastion.SpellBook:GetSpell(385616) -local Ambush = Bastion.SpellBook:GetSpell(8676) -local KeepItRolling = Bastion.SpellBook:GetSpell(381989) -local Audacity = Bastion.SpellBook:GetSpell(381845) -local FindWeakness = Bastion.SpellBook:GetSpell(91023) -local PistolShot = Bastion.SpellBook:GetSpell(185763) -local Opportunity = Bastion.SpellBook:GetSpell(279876) -local OpportunityAura = Bastion.SpellBook:GetSpell(195627) -local GreenskinsWickers = Bastion.SpellBook:GetSpell(386823) -local QuickDraw = Bastion.SpellBook:GetSpell(196938) -local Weaponmaster = Bastion.SpellBook:GetSpell(200733) -local SinisterStrike = Bastion.SpellBook:GetSpell(193315) -local AdrenalineRush = Bastion.SpellBook:GetSpell(13750) -local ImprovedAdrenalineRush = Bastion.SpellBook:GetSpell(395422) -local BladeRush = Bastion.SpellBook:GetSpell(271877) -local MarkedForDeath = Bastion.SpellBook:GetSpell(137619) -local ThistleTea = Bastion.SpellBook:GetSpell(381623) -local PotionOfUnbridledFury = Bastion.SpellBook:GetSpell(169299) -local Bloodlust = Bastion.SpellBook:GetSpell(2825) -local BloodFury = Bastion.SpellBook:GetSpell(20572) -local Berserking = Bastion.SpellBook:GetSpell(26297) -local Fireblood = Bastion.SpellBook:GetSpell(265221) -local AncestralCall = Bastion.SpellBook:GetSpell(274738) -local SliceAndDice = Bastion.SpellBook:GetSpell(315496) -local SwiftSlasher = Bastion.SpellBook:GetSpell(381988) -local ColdBlood = Bastion.SpellBook:GetSpell(382245) -local Dispatch = Bastion.SpellBook:GetSpell(2098) -local Vanish = Bastion.SpellBook:GetSpell(1856) - -local Broadside = Bastion.SpellBook:GetSpell(193356) -local GrandMelee = Bastion.SpellBook:GetSpell(193358) -local SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) -local TrueBearing = Bastion.SpellBook:GetSpell(193359) -local LoadedDice = Bastion.SpellBook:GetSpell(256171) -local BuriedTreasure = Bastion.SpellBook:GetSpell(199600) -local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) - -local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) - local purge = nil - - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end - - if not Player:CanSee(unit) then - return false - end - - if Player:GetDistance(unit) > 40 then - return false - end - - if unit:GetAuras():HasAnyStealableAura() then - purge = unit - return true - end - end) - - if purge == nil then - purge = None - end - - return purge -end) - -local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) - local purge = nil - - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end - - if not Player:CanSee(unit) then - return false - end - - if Player:GetDistance(unit) > 40 then - return false - end - - if Player:InMelee(unit) and unit:IsInterruptible(5) and Player:IsFacing(unit) then - purge = unit - return true - end - end) - - if purge == nil then - purge = None - end - - return purge -end) - -local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) - local tank = nil - - Bastion.UnitManager:EnumFriends(function(unit) - if Player:GetDistance(unit) > 40 then - return false - end - - if not Player:CanSee(unit) then - return false - end - - if unit:IsDead() then - return false - end - - if unit:IsTank() then - tank = unit - return true - end - - return false - end) - - if tank == nil then - tank = None - end - - return tank -end) - -local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) - local explosive = nil - - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end - - if not Player:CanSee(unit) then - return false - end - - if Player:GetDistance(unit) > 40 then - return false - end - - if Player:InMelee(unit) and unit:GetID() == 120651 and Player:IsFacing(unit) then - - explosive = unit - return true - end - end) - - if explosive == nil then - explosive = None - end - - return explosive -end) - -local DefaultAPL = Bastion.APL:New('default') -local StealthAPL = Bastion.APL:New('stealth') -local CDsAPL = Bastion.APL:New('cds') -local FinishAPL = Bastion.APL:New('finish') -local BuildAPL = Bastion.APL:New('build') -local StealthCDsAPL = Bastion.APL:New('stealthcds') - --- # Executed every time the actor is available. --- # Restealth if possible (no vulnerable enemies in combat) --- actions=stealth - -DefaultAPL:AddSpell( - Stealth:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and - not Player:IsAffectingCombat() and not IsMounted() - end - ):SetTarget(Player) -) - --- # Interrupt on cooldown to allow simming interactions with that --- actions+=/kick -DefaultAPL:AddSpell( - Kick:CastableIf(function(self) - return self:IsKnownAndUsable() and KickTarget:Exists() and self:IsInRange(KickTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:IsFacing(Target) - end):SetTarget(KickTarget) -) --- # Checks if we are in an appropriate Stealth state for triggering the Count the Odds bonus --- actions+=/variable,name=stealthed_cto,value=talent.count_the_odds&(stealthed.basic|buff.shadowmeld.up|buff.shadow_dance.up) -DefaultAPL:AddVariable( - 'stealthed_cto', - function() - return CountTheOdds:IsKnown() and (Player:GetAuras():FindMy(Stealth):IsUp() or - Player:GetAuras():FindMy(Shadowmeld):IsUp() or - Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) - end -) - -local function GetRTBCount() - local count = 0 - - if Player:GetAuras():FindMy(Broadside):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(GrandMelee):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(TrueBearing):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(LoadedDice):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(BuriedTreasure):IsUp() then - count = count + 1 - end - - if Player:GetAuras():FindMy(RuthlessPrecision):IsUp() then - count = count + 1 - end - - return count -end - --- # Roll the Bones Reroll Conditions --- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity,value=rtb_buffs<2&(!buff.broadside.up&(!talent.fan_the_hammer|!buff.skull_and_crossbones.up)&!buff.true_bearing.up|buff.loaded_dice.up)|rtb_buffs=2&(buff.buried_treasure.up&buff.grand_melee.up|!buff.broadside.up&!buff.true_bearing.up&buff.loaded_dice.up) -DefaultAPL:AddVariable( - 'rtb_reroll', - function() - if not HiddenOpportunity:IsKnown() then - return GetRTBCount() < 2 and - (not Player:GetAuras():FindMy(Broadside):IsUp() and - (not FanTheHammer:IsKnown() or - not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and - not Player:GetAuras():FindMy(TrueBearing):IsUp() or - Player:GetAuras():FindMy(LoadedDice):IsUp()) or - GetRTBCount() == 2 and - (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and - Player:GetAuras():FindMy(GrandMelee):IsUp() or - not Player:GetAuras():FindMy(Broadside):IsUp() and - not Player:GetAuras():FindMy(TrueBearing):IsUp() and - Player:GetAuras():FindMy(LoadedDice):IsUp()) - end - end -) - --- # Additional Reroll Conditions for Keep it Rolling or Count the Odds --- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity&(talent.keep_it_rolling|talent.count_the_odds),value=variable.rtb_reroll|((rtb_buffs.normal=0&rtb_buffs.longer>=1)&!(buff.broadside.up&buff.true_bearing.up&buff.skull_and_crossbones.up)&!(buff.broadside.remains>39|buff.true_bearing.remains>39|buff.ruthless_precision.remains>39|buff.skull_and_crossbones.remains>39)) -DefaultAPL:AddVariable( - 'rtb_reroll', - function() - if not HiddenOpportunity:IsKnown() then - return GetRTBCount() < 2 and - (not Player:GetAuras():FindMy(Broadside):IsUp() and - (not FanTheHammer:IsKnown() or - not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and - not Player:GetAuras():FindMy(TrueBearing):IsUp() or - Player:GetAuras():FindMy(LoadedDice):IsUp()) or - GetRTBCount() == 2 and - (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and - Player:GetAuras():FindMy(GrandMelee):IsUp() or - not Player:GetAuras():FindMy(Broadside):IsUp() and - not Player:GetAuras():FindMy(TrueBearing):IsUp() and - Player:GetAuras():FindMy(LoadedDice):IsUp()) - end - end -) - --- # With Hidden Opportunity, prioritize rerolling for Skull and Crossbones over everything else --- actions+=/variable,name=rtb_reroll,if=talent.hidden_opportunity,value=!rtb_buffs.will_lose.skull_and_crossbones&(rtb_buffs.will_lose-rtb_buffs.will_lose.grand_melee)<2+buff.loaded_dice.up -DefaultAPL:AddVariable( - 'rtb_reroll', - function() - if HiddenOpportunity:IsKnown() then - return not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and - ((Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) - - (Player:GetAuras():FindMy(GrandMelee):IsUp() and 1 or 0)) < - 2 + (Player:GetAuras():FindMy(LoadedDice):IsUp() and 1 or 0) - end - end -) - --- # Avoid rerolls when we will not have time remaining on the fight or add wave to recoup the opportunity cost of the global --- actions+=/variable,name=rtb_reroll,op=reset,if=!(raid_event.adds.remains>12|raid_event.adds.up&(raid_event.adds.in-raid_event.adds.remains)<6|target.time_to_die>12)|fight_remains<12 -DefaultAPL:AddVariable( - 'rtb_reroll', - function() - if not HiddenOpportunity:IsKnown() then - return GetRTBCount() < 2 and - (not Player:GetAuras():FindMy(Broadside):IsUp() and - (not FanTheHammer:IsKnown() or - not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and - not Player:GetAuras():FindMy(TrueBearing):IsUp() or - Player:GetAuras():FindMy(LoadedDice):IsUp()) or - GetRTBCount() == 2 and - (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and - Player:GetAuras():FindMy(GrandMelee):IsUp() or - not Player:GetAuras():FindMy(Broadside):IsUp() and - not Player:GetAuras():FindMy(TrueBearing):IsUp() and - Player:GetAuras():FindMy(LoadedDice):IsUp()) - end - end -) - --- # Ensure we get full Ambush CP gains and aren't rerolling Count the Odds buffs away --- actions+=/variable,name=ambush_condition,value=combo_points.deficit>=2+talent.improved_ambush+buff.broadside.up&energy>=50&(!talent.count_the_odds|buff.roll_the_bones.remains>=10) -DefaultAPL:AddVariable( - 'ambush_condition', - function() - return Player:GetComboPointsDeficit() >= 2 + (ImprovedAmbush:IsKnown() and 1 or 0) + - (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) and - Player:GetPower() >= 50 and - (not CountTheOdds:IsKnown() or - Player:GetAuras():FindMy(RollTheBones):GetRemainingTime() >= 10) - end -) - --- # Finish at 6 (5 with Summarily Dispatched talented) CP or CP Max-1, whichever is greater of the two --- actions+=/variable,name=finish_condition,value=combo_points>=((cp_max_spend-1)=cp_max_spend -DefaultAPL:AddVariable( - 'finish_condition', - function() - return Player:GetComboPoints() >= - math.min(Player:GetComboPointsMax() - 1, 6 - (SummarilyDispatched:IsKnown() and 1 or 0)) or - Player:GetComboPoints() >= Player:GetComboPointsMax() - end -) - --- # With multiple targets, this variable is checked to decide whether some CDs should be synced with Blade Flurry --- actions+=/variable,name=blade_flurry_sync,value=spell_targets.blade_flurry<2&raid_event.adds.in>20|buff.blade_flurry.remains>1+talent.killing_spree.enabled -DefaultAPL:AddVariable( - 'blade_flurry_sync', - function() - return Player:GetMeleeAttackers() < 2 and - (not Player:GetAuras():FindMy(BladeFlurry):IsUp() or - Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() > 1 + - (KillingSpree:IsKnown() and 1 or 0)) - end -) - --- # Higher priority Stealth list for Count the Odds or true Stealth/Vanish that will break in a single global --- actions+=/call_action_list,name=stealth,if=stealthed.basic|buff.shadowmeld.up -DefaultAPL:AddAPL( - StealthAPL, - function() - return Player:IsStealthed() or Player:GetAuras():FindMy(Shadowmeld):IsUp() - end -) - --- actions+=/call_action_list,name=cds -DefaultAPL:AddAPL( - CDsAPL, - function() - return true - end -) - --- # Lower priority Stealth list for Shadow Dance --- actions+=/call_action_list,name=stealth,if=variable.stealthed_cto -DefaultAPL:AddAPL( - StealthAPL, - function() - return DefaultAPL:GetVariable('stealthed_cto') - end -) - --- actions+=/run_action_list,name=finish,if=variable.finish_condition -DefaultAPL:AddAPL( - FinishAPL, - function() - return DefaultAPL:GetVariable('finish_condition') - end -) - --- actions+=/call_action_list,name=build -DefaultAPL:AddAPL( - BuildAPL, - function() - return true - end -) - --- actions+=/arcane_torrent,if=energy.base_deficit>=15+energy.regen -DefaultAPL:AddSpell( - ArcaneTorrent:CastableIf( - function(self) - return self:IsKnownAndUsable() and Player:GetPowerDeficit() >= 15 + Player:GetPowerRegen() - end - ):SetTarget(Target) - -) - --- actions+=/arcane_pulse -DefaultAPL:AddSpell( - ArcanePulse:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions+=/lights_judgment -DefaultAPL:AddSpell( - LightsJudgment:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions+=/bag_of_tricks -DefaultAPL:AddSpell( - BagOfTricks:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- # Builders --- actions.build=sepsis,target_if=max:target.time_to_die*debuff.between_the_eyes.up,if=target.time_to_die>11&debuff.between_the_eyes.up|fight_remains<11 -BuildAPL:AddSpell( - Sepsis:CastableIf( - function(self) - return self:IsKnownAndUsable() and ((Target:TimeToDie() > 11 and - Target:GetAuras():FindMy(BetweenTheEyes):IsUp()) or - Player:TimeToDie() < 11) - end - ):SetTarget(Target) -) - --- actions.build+=/ghostly_strike,if=debuff.ghostly_strike.remains<=3&(spell_targets.blade_flurry<=2|buff.dreadblades.up)&!buff.subterfuge.up&target.time_to_die>=5 -BuildAPL:AddSpell( - GhostlyStrike:CastableIf( - function(self) - return self:IsKnownAndUsable() and Target:GetAuras():FindMy(GhostlyStrike):GetRemainingTime() <= 3 and - (Player:GetMeleeAttackers() <= 2 or Player:GetAuras():FindMy(Dreadblades):IsUp()) and - not Player:GetAuras():FindMy(Subterfuge):IsUp() and Target:TimeToDie() >= 5 - end - ):SetTarget(Target) -) - --- actions.build+=/echoing_reprimand,if=!buff.dreadblades.up -BuildAPL:AddSpell( - EchoingReprimand:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Dreadblades):IsUp() - end - ):SetTarget(Target) -) - --- # High priority Ambush line to apply Find Weakness or consume Audacity/Sepsis buff before Pistol Shot --- actions.build+=/ambush,if=(talent.hidden_opportunity|talent.keep_it_rolling)&(buff.audacity.up|buff.sepsis_buff.up|buff.subterfuge.up&cooldown.keep_it_rolling.ready)|talent.find_weakness&debuff.find_weakness.down -BuildAPL:AddSpell( - Ambush:CastableIf( - function(self) - return self:IsKnownAndUsable() and ((HiddenOpportunity:IsKnown() or KeepItRolling:IsKnown()) and - (Player:GetAuras():FindMy(Audacity):IsUp() or Player:GetAuras():FindMy(Sepsis):IsUp() or - (Player:GetAuras():FindMy(Subterfuge):IsUp() and KeepItRolling:OnCooldown())) or - (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) - end - ):SetTarget(Target) -) - --- # With Audacity + Hidden Opportunity + Fan the Hammer, use Pistol Shot to proc Audacity any time Ambush is not available --- actions.build+=/pistol_shot,if=talent.fan_the_hammer&talent.audacity&talent.hidden_opportunity&buff.opportunity.up&!buff.audacity.up&!buff.subterfuge.up&!buff.shadow_dance.up -BuildAPL:AddSpell( - PistolShot:CastableIf( - function(self) - return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and Audacity:IsKnown() and - HiddenOpportunity:IsKnown() and - Player:GetAuras():FindMy(OpportunityAura):IsUp() and not Player:GetAuras():FindMy(Audacity):IsUp() and - not Player:GetAuras():FindMy(Subterfuge):IsUp() and not Player:GetAuras():FindMy(ShadowDance):IsUp() - end - ):SetTarget(Target) -) - --- # Use Greenskins Wickers buff immediately with Opportunity unless running Fan the Hammer --- actions.build+=/pistol_shot,if=buff.greenskins_wickers.up&(!talent.fan_the_hammer&buff.opportunity.up|buff.greenskins_wickers.remains<1.5) -BuildAPL:AddSpell( - PistolShot:CastableIf( - function(self) - return self:IsKnownAndUsable() and Player:GetAuras():FindMy(GreenskinsWickers):IsUp() and - (not FanTheHammer:IsKnown() and Player:GetAuras():FindMy(OpportunityAura):IsUp() or - Player:GetAuras():FindMy(GreenskinsWickers):GetRemainingTime() < 1.5) - end - ):SetTarget(Target) -) - --- const int stacks = 1 + as( p()->talent.outlaw.fan_the_hammer->effectN( 1 ).base_value() ); -local function MaxOpportunity() - return 1 + (FanTheHammer:IsKnown() and 1 or 0) -end - --- # With Fan the Hammer, consume Opportunity at max stacks or if we will get max 4+ CP and Dreadblades is not up --- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&(buff.opportunity.stack>=buff.opportunity.max_stack|buff.opportunity.remains<2) -BuildAPL:AddSpell( - PistolShot:CastableIf( - function(self) - return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and - Player:GetAuras():FindMy(OpportunityAura):IsUp() and - ( - Player:GetAuras():FindMy(OpportunityAura):GetCount() >= - MaxOpportunity() - or - Player:GetAuras():FindMy(OpportunityAura):GetRemainingTime() < 2) - end - ):SetTarget(Target) -) - --- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&combo_points.deficit>((1+talent.quick_draw)*talent.fan_the_hammer.rank)&!buff.dreadblades.up&(!talent.hidden_opportunity|!buff.subterfuge.up&!buff.shadow_dance.up) -BuildAPL:AddSpell( - PistolShot:CastableIf( - function(self) - return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and - Player:GetAuras():FindMy(OpportunityAura):IsUp() and - Player:GetComboPointsDeficit() > ((1 + (QuickDraw:IsKnown() and 1 or 0)) * 1) and - not Player:GetAuras():FindMy(Dreadblades):IsUp() and - (not HiddenOpportunity:IsKnown() or not Player:GetAuras():FindMy(Subterfuge):IsUp() and - not Player:GetAuras():FindMy(ShadowDance):IsUp()) - end - ):SetTarget(Target) -) - --- actions.build+=/pool_resource,for_next=1 --- actions.build+=/ambush,if=talent.hidden_opportunity|talent.find_weakness&debuff.find_weakness.down -BuildAPL:AddSpell( - Ambush:CastableIf( - function(self) - return self:IsKnownAndUsable() and (HiddenOpportunity:IsKnown() or - (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) - end - ):SetTarget(Target) -) - --- # Use Pistol Shot with Opportunity if Combat Potency won't overcap energy, when it will exactly cap CP, or when using Quick Draw --- actions.build+=/pistol_shot,if=!talent.fan_the_hammer&buff.opportunity.up&(energy.base_deficit>energy.regen*1.5|!talent.weaponmaster&combo_points.deficit<=1+buff.broadside.up|talent.quick_draw.enabled|talent.audacity.enabled&!buff.audacity.up) -BuildAPL:AddSpell( - PistolShot:CastableIf( - function(self) - return self:IsKnownAndUsable() and not FanTheHammer:IsKnown() and - Player:GetAuras():FindMy(OpportunityAura):IsUp() and - (Player:GetPowerDeficit() > Player:GetPowerRegen() * 1.5 or - (not Weaponmaster:IsKnown() and - Player:GetComboPointsDeficit() <= 1 + (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0)) or - QuickDraw:IsKnown() or - (Audacity:IsKnown() and not Player:GetAuras():FindMy(Audacity):IsUp())) - end - ):SetTarget(Target) -) - - --- actions.build+=/sinister_strike -BuildAPL:AddSpell( - SinisterStrike:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- # Cooldowns --- actions.cds=adrenaline_rush,if=!buff.adrenaline_rush.up&(!talent.improved_adrenaline_rush|combo_points<=2) -CDsAPL:AddSpell( - AdrenalineRush:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(AdrenalineRush):IsUp() and - (not ImprovedAdrenalineRush:IsKnown() or Player:GetComboPoints() <= 2) - end - ):SetTarget(Target) -) - --- actions.cds+=/blade_flurry,if=spell_targets>=2&buff.blade_flurry.remains= 2 and - Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() < Player:GetGCD() - end - ):SetTarget(Target) -) - --- actions.cds+=/roll_the_bones,if=buff.dreadblades.down&(rtb_buffs.total=0|variable.rtb_reroll) -CDsAPL:AddSpell( - RollTheBones:CastableIf( - function(self) - return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Dreadblades):IsDown() and - (GetRTBCount() == 0 or - DefaultAPL:GetVariable("rtb_reroll")) - end - ):SetTarget(Target) -) - --- actions.cds+=/keep_it_rolling,if=!variable.rtb_reroll&(buff.broadside.up+buff.true_bearing.up+buff.skull_and_crossbones.up+buff.ruthless_precision.up)>2&(buff.shadow_dance.down|rtb_buffs>=6) -CDsAPL:AddSpell( - RollTheBones:CastableIf( - function(self) - return self:IsKnownAndUsable() and not DefaultAPL:GetVariable("rtb_reroll") and - ((Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) + - (Player:GetAuras():FindMy(TrueBearing):IsUp() and 1 or 0) + - (Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) + - (Player:GetAuras():FindMy(RuthlessPrecision):IsUp() and 1 or 0)) > 2 and - (Player:GetAuras():FindMy(ShadowDance):IsDown() or - GetRTBCount() >= 6) - end - ):SetTarget(Target) -) - --- actions.cds+=/blade_rush,if=variable.blade_flurry_sync&!buff.dreadblades.up&(energy.base_time_to_max>4+stealthed.rogue-spell_targets%3) -CDsAPL:AddSpell( - BladeRush:CastableIf( - function(self) - return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and - not Player:GetAuras():FindMy(Dreadblades):IsUp() and - (Player:GetTimeToPowerPercent() > 4 + (Player:GetAuras():FindMy(Subterfuge):IsUp() and 1 or 0) + - (Player:GetAuras():FindMy(ShadowDance):IsUp() and 1 or 0) - Target:GetMeleeAttackers() % 3) - end - ):SetTarget(Target) -) - --- actions.cds+=/call_action_list,name=stealth_cds,if=!stealthed.all|talent.count_the_odds&!variable.stealthed_cto -CDsAPL:AddAPL( - StealthCDsAPL, - function() - return not Player:GetAuras():FindMy(Stealth):IsUp() or - (CountTheOdds:IsKnown() and not DefaultAPL:GetVariable("stealthed_cto")) - end -) - --- actions.cds+=/dreadblades,if=!(variable.stealthed_cto|stealthed.basic|talent.hidden_opportunity&stealthed.rogue)&combo_points<=2&(!talent.marked_for_death|!cooldown.marked_for_death.ready)&target.time_to_die>=10 -CDsAPL:AddSpell( - Dreadblades:CastableIf( - function(self) - return self:IsKnownAndUsable() and - not (DefaultAPL:GetVariable("stealthed_cto") or Player:GetAuras():FindMy(Stealth):IsUp() or - (HiddenOpportunity:IsKnown() and Player:GetAuras():FindMy(Subterfuge):IsUp())) and - Player:GetComboPoints() <= 2 and - (not MarkedForDeath:IsKnown() or not MarkedForDeath:CooldownUp()) and - Target:TimeToDie() >= 10 - end - ):SetTarget(Target) -) - --- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or without any CP. --- actions.cds+=/marked_for_death,line_cd=1.5,target_if=min:target.time_to_die,if=raid_event.adds.up&(target.time_to_die=cp_max_spend-1)&!buff.dreadblades.up - --- # If no adds will die within the next 30s, use MfD on boss without any CP. --- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend-1&!buff.dreadblades.up - --- actions.cds+=/thistle_tea,if=!buff.thistle_tea.up&(energy.base_deficit>=100|fight_remains= 100 or Target:TimeToDie() < ThistleTea:Charges() * 6) - end - ):SetTarget(Target) -) - --- actions.cds+=/killing_spree,if=variable.blade_flurry_sync&!stealthed.rogue&debuff.between_the_eyes.up&energy.base_time_to_max>4 -CDsAPL:AddSpell( - KillingSpree:CastableIf( - function(self) - return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and - not Player:GetAuras():FindMy(Subterfuge):IsUp() and - Target:GetAuras():FindMy(BetweenTheEyes):IsUp() and - Player:GetTimeToPowerPercent() > 4 - end - ):SetTarget(Target) -) - --- actions.cds+=/shadowmeld,if=!stealthed.all&(talent.count_the_odds&variable.finish_condition|!talent.weaponmaster.enabled&variable.ambush_condition) -CDsAPL:AddSpell( - Shadowmeld:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and - (CountTheOdds:IsKnown() and DefaultAPL:GetVariable("finish_condition") or - not Weaponmaster:IsKnown() and DefaultAPL:GetVariable("ambush_condition")) - end - ):SetTarget(Target) -) - --- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.adrenaline_rush.up --- CDsAPL:AddItem( --- PotionOfUnbridledFury:CastableIf( --- function(self) --- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Bloodlust):IsUp() or Target:TimeToDie() < 30 or --- Player:GetAuras():FindMy(AdrenalineRush):IsUp() --- end --- ):SetTarget(Target) --- ) - --- actions.cds+=/blood_fury -CDsAPL:AddSpell( - BloodFury:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions.cds+=/berserking -CDsAPL:AddSpell( - Berserking:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions.cds+=/fireblood -CDsAPL:AddSpell( - Fireblood:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions.cds+=/ancestral_call -CDsAPL:AddSpell( - AncestralCall:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- # Default conditions for usable items. --- actions.cds+=/use_item,name=manic_grieftorch,if=!stealthed.all&!buff.adrenaline_rush.up|fight_remains<5 --- actions.cds+=/use_item,name=stormeaters_boon,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<10 --- actions.cds+=/use_item,name=windscar_whetstone,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<7 --- actions.cds+=/use_items,slots=trinket1,if=debuff.between_the_eyes.up|trinket.1.has_stat.any_dps|fight_remains<=20 --- actions.cds+=/use_items,slots=trinket2,if=debuff.between_the_eyes.up|trinket.2.has_stat.any_dps|fight_remains<=20 - --- # Finishers BtE to keep the Crit debuff up, if RP is up, or for Greenskins, unless the target is about to die. --- actions.finish=between_the_eyes,if=target.time_to_die>3&(debuff.between_the_eyes.remains<4|talent.greenskins_wickers&!buff.greenskins_wickers.up|!talent.greenskins_wickers&buff.ruthless_precision.up) -FinishAPL:AddSpell( - BetweenTheEyes:CastableIf( - function(self) - return self:IsKnownAndUsable() and Target:TimeToDie() > 3 and - (Target:GetAuras():FindMy(BetweenTheEyes):GetRemainingTime() < 4 or - GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(GreenskinsWickers):IsDown() or - not GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(RuthlessPrecision):IsUp()) - end - ):SetTarget(Target) -) - --- actions.finish+=/slice_and_dice,if=buff.slice_and_dice.remains=cp_max_spend) -FinishAPL:AddSpell( - SliceAndDice:CastableIf( - function(self) - return self:IsKnownAndUsable() and - Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < Target:TimeToDie() and - Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 6 and - (not SwiftSlasher:IsKnown() or Player:GetComboPoints() >= Player:GetComboPointsMax()) - end - ):SetTarget(Target) -) - --- actions.finish+=/cold_blood -FinishAPL:AddSpell( - ColdBlood:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- actions.finish+=/dispatch -FinishAPL:AddSpell( - Dispatch:CastableIf( - function(self) - return self:IsKnownAndUsable() and true - end - ):SetTarget(Target) -) - --- # Stealth --- actions.stealth=blade_flurry,if=talent.subterfuge&talent.hidden_opportunity&spell_targets>=2&!buff.blade_flurry.up -StealthAPL:AddSpell( - BladeFlurry:CastableIf( - function(self) - return self:IsKnownAndUsable() and Subterfuge:IsKnown() and HiddenOpportunity:IsKnown() and - Target:GetMeleeAttackers() >= 2 and - not Player:GetAuras():FindMy(BladeFlurry):IsUp() - end - ):SetTarget(Target) -) - --- actions.stealth+=/cold_blood,if=variable.finish_condition -StealthAPL:AddSpell( - ColdBlood:CastableIf( - function(self) - return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') - end - ):SetTarget(Target) -) - --- actions.stealth+=/dispatch,if=variable.finish_condition -StealthAPL:AddSpell( - Dispatch:CastableIf( - function(self) - return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') - end - ):SetTarget(Target) -) - --- actions.stealth+=/ambush,if=variable.stealthed_cto|stealthed.basic&talent.find_weakness&!debuff.find_weakness.up|talent.hidden_opportunity -StealthAPL:AddSpell( - Ambush:CastableIf( - function(self) - return self:IsKnownAndUsable() and (DefaultAPL:GetVariable('stealthed_cto') or - Player:IsStealthed() and FindWeakness:IsKnown() and not Target:GetAuras():FindMy(FindWeakness):IsUp() or - HiddenOpportunity:IsKnown()) - end - ):SetTarget(Target) -) - --- # Stealth Cooldowns --- actions.stealth_cds=variable,name=vanish_condition,value=talent.hidden_opportunity|!talent.shadow_dance|!cooldown.shadow_dance.ready -StealthAPL:AddVariable( - 'vanish_condition', - function() - return HiddenOpportunity:IsKnown() or not ShadowDance:IsKnown() or not ShadowDance:CooldownUp() - end -) - --- actions.stealth_cds+=/variable,name=vanish_opportunity_condition,value=!talent.shadow_dance&talent.fan_the_hammer.rank+talent.quick_draw+talent.audacity120&(variable.finish_condition|talent.hidden_opportunity)) -StealthAPL:AddSpell( - ShadowDance:CastableIf( - function(self) - return self:IsKnownAndUsable() and KeepItRolling:IsKnown() and - StealthAPL:GetVariable('shadow_dance_condition') and - (KeepItRolling:GetCooldownRemaining() <= 30 or - KeepItRolling:GetCooldownRemaining() > 120 and - (DefaultAPL:GetVariable('finish_condition') or HiddenOpportunity:IsKnown())) - end - ):SetTarget(Target) -) - -OutlawModule:Sync(function() - print(BetweenTheEyes:IsKnownAndUsable()) - DefaultAPL:Execute() -end) - -Bastion:Register(OutlawModule) +-- local Tinkr, Bastion = ... + +-- local OutlawModule = Bastion.Module:New('outlaw') +-- local Evaluator = Tinkr.Util.Evaluator +-- local Player = Bastion.UnitManager:Get('player') +-- local None = Bastion.UnitManager:Get('none') +-- local Target = Bastion.UnitManager:Get('target') + +-- local Stealth = Bastion.SpellBook:GetSpell(115191) +-- local Kick = Bastion.SpellBook:GetSpell(1766) +-- local CountTheOdds = Bastion.SpellBook:GetSpell(381982) +-- local Shadowmeld = Bastion.SpellBook:GetSpell(58984) +-- local ShadowDance = Bastion.SpellBook:GetSpell(185313) +-- local ShadowDanceAura = Bastion.SpellBook:GetSpell(185422) +-- local HiddenOpportunity = Bastion.SpellBook:GetSpell(383281) +-- local RollTheBones = Bastion.SpellBook:GetSpell(315508) +-- local FanTheHammer = Bastion.SpellBook:GetSpell(381846) +-- local ImprovedAmbush = Bastion.SpellBook:GetSpell(381620) +-- local SummarilyDispatched = Bastion.SpellBook:GetSpell(381990) +-- local BladeFlurry = Bastion.SpellBook:GetSpell(13877) +-- local KillingSpree = Bastion.SpellBook:GetSpell(51690) +-- local ArcaneTorrent = Bastion.SpellBook:GetSpell(25046) +-- local ArcanePulse = Bastion.SpellBook:GetSpell(260364) +-- local LightsJudgment = Bastion.SpellBook:GetSpell(255647) +-- local BagOfTricks = Bastion.SpellBook:GetSpell(312411) +-- local Sepsis = Bastion.SpellBook:GetSpell(385408) +-- local BetweenTheEyes = Bastion.SpellBook:GetSpell(315341) +-- local GhostlyStrike = Bastion.SpellBook:GetSpell(196937) +-- local Dreadblades = Bastion.SpellBook:GetSpell(343142) +-- local Subterfuge = Bastion.SpellBook:GetSpell(108208) +-- local EchoingReprimand = Bastion.SpellBook:GetSpell(385616) +-- local Ambush = Bastion.SpellBook:GetSpell(8676) +-- local KeepItRolling = Bastion.SpellBook:GetSpell(381989) +-- local Audacity = Bastion.SpellBook:GetSpell(381845) +-- local FindWeakness = Bastion.SpellBook:GetSpell(91023) +-- local PistolShot = Bastion.SpellBook:GetSpell(185763) +-- local Opportunity = Bastion.SpellBook:GetSpell(279876) +-- local OpportunityAura = Bastion.SpellBook:GetSpell(195627) +-- local GreenskinsWickers = Bastion.SpellBook:GetSpell(386823) +-- local QuickDraw = Bastion.SpellBook:GetSpell(196938) +-- local Weaponmaster = Bastion.SpellBook:GetSpell(200733) +-- local SinisterStrike = Bastion.SpellBook:GetSpell(193315) +-- local AdrenalineRush = Bastion.SpellBook:GetSpell(13750) +-- local ImprovedAdrenalineRush = Bastion.SpellBook:GetSpell(395422) +-- local BladeRush = Bastion.SpellBook:GetSpell(271877) +-- local MarkedForDeath = Bastion.SpellBook:GetSpell(137619) +-- local ThistleTea = Bastion.SpellBook:GetSpell(381623) +-- local PotionOfUnbridledFury = Bastion.SpellBook:GetSpell(169299) +-- local Bloodlust = Bastion.SpellBook:GetSpell(2825) +-- local BloodFury = Bastion.SpellBook:GetSpell(20572) +-- local Berserking = Bastion.SpellBook:GetSpell(26297) +-- local Fireblood = Bastion.SpellBook:GetSpell(265221) +-- local AncestralCall = Bastion.SpellBook:GetSpell(274738) +-- local SliceAndDice = Bastion.SpellBook:GetSpell(315496) +-- local SwiftSlasher = Bastion.SpellBook:GetSpell(381988) +-- local ColdBlood = Bastion.SpellBook:GetSpell(382245) +-- local Dispatch = Bastion.SpellBook:GetSpell(2098) +-- local Vanish = Bastion.SpellBook:GetSpell(1856) + +-- local Broadside = Bastion.SpellBook:GetSpell(193356) +-- local GrandMelee = Bastion.SpellBook:GetSpell(193358) +-- local SkullAndCrossbones = Bastion.SpellBook:GetSpell(199603) +-- local TrueBearing = Bastion.SpellBook:GetSpell(193359) +-- local LoadedDice = Bastion.SpellBook:GetSpell(256171) +-- local BuriedTreasure = Bastion.SpellBook:GetSpell(199600) +-- local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) + +-- local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) +-- local purge = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if unit:GetAuras():HasAnyStealableAura() then +-- purge = unit +-- return true +-- end +-- end) + +-- if purge == nil then +-- purge = None +-- end + +-- return purge +-- end) + +-- local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) +-- local purge = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if Player:InMelee(unit) and unit:IsInterruptible(5) and Player:IsFacing(unit) then +-- purge = unit +-- return true +-- end +-- end) + +-- if purge == nil then +-- purge = None +-- end + +-- return purge +-- end) + +-- local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) +-- local tank = nil + +-- Bastion.UnitManager:EnumFriends(function(unit) +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if unit:IsDead() then +-- return false +-- end + +-- if unit:IsTank() then +-- tank = unit +-- return true +-- end + +-- return false +-- end) + +-- if tank == nil then +-- tank = None +-- end + +-- return tank +-- end) + +-- local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) +-- local explosive = nil + +-- Bastion.UnitManager:EnumEnemies(function(unit) +-- if unit:IsDead() then +-- return false +-- end + +-- if not Player:CanSee(unit) then +-- return false +-- end + +-- if Player:GetDistance(unit) > 40 then +-- return false +-- end + +-- if Player:InMelee(unit) and unit:GetID() == 120651 and Player:IsFacing(unit) then + +-- explosive = unit +-- return true +-- end +-- end) + +-- if explosive == nil then +-- explosive = None +-- end + +-- return explosive +-- end) + +-- local DefaultAPL = Bastion.APL:New('default') +-- local StealthAPL = Bastion.APL:New('stealth') +-- local CDsAPL = Bastion.APL:New('cds') +-- local FinishAPL = Bastion.APL:New('finish') +-- local BuildAPL = Bastion.APL:New('build') +-- local StealthCDsAPL = Bastion.APL:New('stealthcds') + +-- -- # Executed every time the actor is available. +-- -- # Restealth if possible (no vulnerable enemies in combat) +-- -- actions=stealth + +-- DefaultAPL:AddSpell( +-- Stealth:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and +-- not Player:IsAffectingCombat() and not IsMounted() +-- end +-- ):SetTarget(Player) +-- ) + +-- -- # Interrupt on cooldown to allow simming interactions with that +-- -- actions+=/kick +-- DefaultAPL:AddSpell( +-- Kick:CastableIf(function(self) +-- return self:IsKnownAndUsable() and KickTarget:Exists() and self:IsInRange(KickTarget) and +-- self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and Player:IsFacing(Target) +-- end):SetTarget(KickTarget) +-- ) +-- -- # Checks if we are in an appropriate Stealth state for triggering the Count the Odds bonus +-- -- actions+=/variable,name=stealthed_cto,value=talent.count_the_odds&(stealthed.basic|buff.shadowmeld.up|buff.shadow_dance.up) +-- DefaultAPL:AddVariable( +-- 'stealthed_cto', +-- function() +-- return CountTheOdds:IsKnown() and (Player:GetAuras():FindMy(Stealth):IsUp() or +-- Player:GetAuras():FindMy(Shadowmeld):IsUp() or +-- Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) +-- end +-- ) + +-- local function GetRTBCount() +-- local count = 0 + +-- if Player:GetAuras():FindMy(Broadside):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(GrandMelee):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(TrueBearing):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(LoadedDice):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(BuriedTreasure):IsUp() then +-- count = count + 1 +-- end + +-- if Player:GetAuras():FindMy(RuthlessPrecision):IsUp() then +-- count = count + 1 +-- end + +-- return count +-- end + +-- -- # Roll the Bones Reroll Conditions +-- -- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity,value=rtb_buffs<2&(!buff.broadside.up&(!talent.fan_the_hammer|!buff.skull_and_crossbones.up)&!buff.true_bearing.up|buff.loaded_dice.up)|rtb_buffs=2&(buff.buried_treasure.up&buff.grand_melee.up|!buff.broadside.up&!buff.true_bearing.up&buff.loaded_dice.up) +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # Additional Reroll Conditions for Keep it Rolling or Count the Odds +-- -- actions+=/variable,name=rtb_reroll,if=!talent.hidden_opportunity&(talent.keep_it_rolling|talent.count_the_odds),value=variable.rtb_reroll|((rtb_buffs.normal=0&rtb_buffs.longer>=1)&!(buff.broadside.up&buff.true_bearing.up&buff.skull_and_crossbones.up)&!(buff.broadside.remains>39|buff.true_bearing.remains>39|buff.ruthless_precision.remains>39|buff.skull_and_crossbones.remains>39)) +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # With Hidden Opportunity, prioritize rerolling for Skull and Crossbones over everything else +-- -- actions+=/variable,name=rtb_reroll,if=talent.hidden_opportunity,value=!rtb_buffs.will_lose.skull_and_crossbones&(rtb_buffs.will_lose-rtb_buffs.will_lose.grand_melee)<2+buff.loaded_dice.up +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if HiddenOpportunity:IsKnown() then +-- return not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and +-- ((Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) - +-- (Player:GetAuras():FindMy(GrandMelee):IsUp() and 1 or 0)) < +-- 2 + (Player:GetAuras():FindMy(LoadedDice):IsUp() and 1 or 0) +-- end +-- end +-- ) + +-- -- # Avoid rerolls when we will not have time remaining on the fight or add wave to recoup the opportunity cost of the global +-- -- actions+=/variable,name=rtb_reroll,op=reset,if=!(raid_event.adds.remains>12|raid_event.adds.up&(raid_event.adds.in-raid_event.adds.remains)<6|target.time_to_die>12)|fight_remains<12 +-- DefaultAPL:AddVariable( +-- 'rtb_reroll', +-- function() +-- if not HiddenOpportunity:IsKnown() then +-- return GetRTBCount() < 2 and +-- (not Player:GetAuras():FindMy(Broadside):IsUp() and +-- (not FanTheHammer:IsKnown() or +-- not Player:GetAuras():FindMy(SkullAndCrossbones):IsUp()) and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() or +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) or +-- GetRTBCount() == 2 and +-- (Player:GetAuras():FindMy(BuriedTreasure):IsUp() and +-- Player:GetAuras():FindMy(GrandMelee):IsUp() or +-- not Player:GetAuras():FindMy(Broadside):IsUp() and +-- not Player:GetAuras():FindMy(TrueBearing):IsUp() and +-- Player:GetAuras():FindMy(LoadedDice):IsUp()) +-- end +-- end +-- ) + +-- -- # Ensure we get full Ambush CP gains and aren't rerolling Count the Odds buffs away +-- -- actions+=/variable,name=ambush_condition,value=combo_points.deficit>=2+talent.improved_ambush+buff.broadside.up&energy>=50&(!talent.count_the_odds|buff.roll_the_bones.remains>=10) +-- DefaultAPL:AddVariable( +-- 'ambush_condition', +-- function() +-- return Player:GetComboPointsDeficit() >= 2 + (ImprovedAmbush:IsKnown() and 1 or 0) + +-- (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) and +-- Player:GetPower() >= 50 and +-- (not CountTheOdds:IsKnown() or +-- Player:GetAuras():FindMy(RollTheBones):GetRemainingTime() >= 10) +-- end +-- ) + +-- -- # Finish at 6 (5 with Summarily Dispatched talented) CP or CP Max-1, whichever is greater of the two +-- -- actions+=/variable,name=finish_condition,value=combo_points>=((cp_max_spend-1)=cp_max_spend +-- DefaultAPL:AddVariable( +-- 'finish_condition', +-- function() +-- return Player:GetComboPoints() >= +-- math.min(Player:GetComboPointsMax() - 1, 6 - (SummarilyDispatched:IsKnown() and 1 or 0)) or +-- Player:GetComboPoints() >= Player:GetComboPointsMax() +-- end +-- ) + +-- -- # With multiple targets, this variable is checked to decide whether some CDs should be synced with Blade Flurry +-- -- actions+=/variable,name=blade_flurry_sync,value=spell_targets.blade_flurry<2&raid_event.adds.in>20|buff.blade_flurry.remains>1+talent.killing_spree.enabled +-- DefaultAPL:AddVariable( +-- 'blade_flurry_sync', +-- function() +-- return Player:GetMeleeAttackers() < 2 and +-- (not Player:GetAuras():FindMy(BladeFlurry):IsUp() or +-- Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() > 1 + +-- (KillingSpree:IsKnown() and 1 or 0)) +-- end +-- ) + +-- -- # Higher priority Stealth list for Count the Odds or true Stealth/Vanish that will break in a single global +-- -- actions+=/call_action_list,name=stealth,if=stealthed.basic|buff.shadowmeld.up +-- DefaultAPL:AddAPL( +-- StealthAPL, +-- function() +-- return Player:IsStealthed() or Player:GetAuras():FindMy(Shadowmeld):IsUp() +-- end +-- ) + +-- -- actions+=/call_action_list,name=cds +-- DefaultAPL:AddAPL( +-- CDsAPL, +-- function() +-- return true +-- end +-- ) + +-- -- # Lower priority Stealth list for Shadow Dance +-- -- actions+=/call_action_list,name=stealth,if=variable.stealthed_cto +-- DefaultAPL:AddAPL( +-- StealthAPL, +-- function() +-- return DefaultAPL:GetVariable('stealthed_cto') +-- end +-- ) + +-- -- actions+=/run_action_list,name=finish,if=variable.finish_condition +-- DefaultAPL:AddAPL( +-- FinishAPL, +-- function() +-- return DefaultAPL:GetVariable('finish_condition') +-- end +-- ) + +-- -- actions+=/call_action_list,name=build +-- DefaultAPL:AddAPL( +-- BuildAPL, +-- function() +-- return true +-- end +-- ) + +-- -- actions+=/arcane_torrent,if=energy.base_deficit>=15+energy.regen +-- DefaultAPL:AddSpell( +-- ArcaneTorrent:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetPowerDeficit() >= 15 + Player:GetPowerRegen() +-- end +-- ):SetTarget(Target) + +-- ) + +-- -- actions+=/arcane_pulse +-- DefaultAPL:AddSpell( +-- ArcanePulse:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions+=/lights_judgment +-- DefaultAPL:AddSpell( +-- LightsJudgment:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions+=/bag_of_tricks +-- DefaultAPL:AddSpell( +-- BagOfTricks:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Builders +-- -- actions.build=sepsis,target_if=max:target.time_to_die*debuff.between_the_eyes.up,if=target.time_to_die>11&debuff.between_the_eyes.up|fight_remains<11 +-- BuildAPL:AddSpell( +-- Sepsis:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and ((Target:TimeToDie() > 11 and +-- Target:GetAuras():FindMy(BetweenTheEyes):IsUp()) or +-- Player:TimeToDie() < 11) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/ghostly_strike,if=debuff.ghostly_strike.remains<=3&(spell_targets.blade_flurry<=2|buff.dreadblades.up)&!buff.subterfuge.up&target.time_to_die>=5 +-- BuildAPL:AddSpell( +-- GhostlyStrike:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Target:GetAuras():FindMy(GhostlyStrike):GetRemainingTime() <= 3 and +-- (Player:GetMeleeAttackers() <= 2 or Player:GetAuras():FindMy(Dreadblades):IsUp()) and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and Target:TimeToDie() >= 5 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/echoing_reprimand,if=!buff.dreadblades.up +-- BuildAPL:AddSpell( +-- EchoingReprimand:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Dreadblades):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # High priority Ambush line to apply Find Weakness or consume Audacity/Sepsis buff before Pistol Shot +-- -- actions.build+=/ambush,if=(talent.hidden_opportunity|talent.keep_it_rolling)&(buff.audacity.up|buff.sepsis_buff.up|buff.subterfuge.up&cooldown.keep_it_rolling.ready)|talent.find_weakness&debuff.find_weakness.down +-- BuildAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and ((HiddenOpportunity:IsKnown() or KeepItRolling:IsKnown()) and +-- (Player:GetAuras():FindMy(Audacity):IsUp() or Player:GetAuras():FindMy(Sepsis):IsUp() or +-- (Player:GetAuras():FindMy(Subterfuge):IsUp() and KeepItRolling:OnCooldown())) or +-- (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # With Audacity + Hidden Opportunity + Fan the Hammer, use Pistol Shot to proc Audacity any time Ambush is not available +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&talent.audacity&talent.hidden_opportunity&buff.opportunity.up&!buff.audacity.up&!buff.subterfuge.up&!buff.shadow_dance.up +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and Audacity:IsKnown() and +-- HiddenOpportunity:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and not Player:GetAuras():FindMy(Audacity):IsUp() and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and not Player:GetAuras():FindMy(ShadowDance):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Use Greenskins Wickers buff immediately with Opportunity unless running Fan the Hammer +-- -- actions.build+=/pistol_shot,if=buff.greenskins_wickers.up&(!talent.fan_the_hammer&buff.opportunity.up|buff.greenskins_wickers.remains<1.5) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(GreenskinsWickers):IsUp() and +-- (not FanTheHammer:IsKnown() and Player:GetAuras():FindMy(OpportunityAura):IsUp() or +-- Player:GetAuras():FindMy(GreenskinsWickers):GetRemainingTime() < 1.5) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- const int stacks = 1 + as( p()->talent.outlaw.fan_the_hammer->effectN( 1 ).base_value() ); +-- local function MaxOpportunity() +-- return 1 + (FanTheHammer:IsKnown() and 1 or 0) +-- end + +-- -- # With Fan the Hammer, consume Opportunity at max stacks or if we will get max 4+ CP and Dreadblades is not up +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&(buff.opportunity.stack>=buff.opportunity.max_stack|buff.opportunity.remains<2) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- ( +-- Player:GetAuras():FindMy(OpportunityAura):GetCount() >= +-- MaxOpportunity() +-- or +-- Player:GetAuras():FindMy(OpportunityAura):GetRemainingTime() < 2) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/pistol_shot,if=talent.fan_the_hammer&buff.opportunity.up&combo_points.deficit>((1+talent.quick_draw)*talent.fan_the_hammer.rank)&!buff.dreadblades.up&(!talent.hidden_opportunity|!buff.subterfuge.up&!buff.shadow_dance.up) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- Player:GetComboPointsDeficit() > ((1 + (QuickDraw:IsKnown() and 1 or 0)) * 1) and +-- not Player:GetAuras():FindMy(Dreadblades):IsUp() and +-- (not HiddenOpportunity:IsKnown() or not Player:GetAuras():FindMy(Subterfuge):IsUp() and +-- not Player:GetAuras():FindMy(ShadowDance):IsUp()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.build+=/pool_resource,for_next=1 +-- -- actions.build+=/ambush,if=talent.hidden_opportunity|talent.find_weakness&debuff.find_weakness.down +-- BuildAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and (HiddenOpportunity:IsKnown() or +-- (FindWeakness:IsKnown() and Target:GetAuras():FindMy(FindWeakness):IsDown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Use Pistol Shot with Opportunity if Combat Potency won't overcap energy, when it will exactly cap CP, or when using Quick Draw +-- -- actions.build+=/pistol_shot,if=!talent.fan_the_hammer&buff.opportunity.up&(energy.base_deficit>energy.regen*1.5|!talent.weaponmaster&combo_points.deficit<=1+buff.broadside.up|talent.quick_draw.enabled|talent.audacity.enabled&!buff.audacity.up) +-- BuildAPL:AddSpell( +-- PistolShot:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not FanTheHammer:IsKnown() and +-- Player:GetAuras():FindMy(OpportunityAura):IsUp() and +-- (Player:GetPowerDeficit() > Player:GetPowerRegen() * 1.5 or +-- (not Weaponmaster:IsKnown() and +-- Player:GetComboPointsDeficit() <= 1 + (Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0)) or +-- QuickDraw:IsKnown() or +-- (Audacity:IsKnown() and not Player:GetAuras():FindMy(Audacity):IsUp())) +-- end +-- ):SetTarget(Target) +-- ) + + +-- -- actions.build+=/sinister_strike +-- BuildAPL:AddSpell( +-- SinisterStrike:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Cooldowns +-- -- actions.cds=adrenaline_rush,if=!buff.adrenaline_rush.up&(!talent.improved_adrenaline_rush|combo_points<=2) +-- CDsAPL:AddSpell( +-- AdrenalineRush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(AdrenalineRush):IsUp() and +-- (not ImprovedAdrenalineRush:IsKnown() or Player:GetComboPoints() <= 2) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/blade_flurry,if=spell_targets>=2&buff.blade_flurry.remains= 2 and +-- Player:GetAuras():FindMy(BladeFlurry):GetRemainingTime() < Player:GetGCD() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/roll_the_bones,if=buff.dreadblades.down&(rtb_buffs.total=0|variable.rtb_reroll) +-- CDsAPL:AddSpell( +-- RollTheBones:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Dreadblades):IsDown() and +-- (GetRTBCount() == 0 or +-- DefaultAPL:GetVariable("rtb_reroll")) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/keep_it_rolling,if=!variable.rtb_reroll&(buff.broadside.up+buff.true_bearing.up+buff.skull_and_crossbones.up+buff.ruthless_precision.up)>2&(buff.shadow_dance.down|rtb_buffs>=6) +-- CDsAPL:AddSpell( +-- RollTheBones:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not DefaultAPL:GetVariable("rtb_reroll") and +-- ((Player:GetAuras():FindMy(Broadside):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(TrueBearing):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(SkullAndCrossbones):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(RuthlessPrecision):IsUp() and 1 or 0)) > 2 and +-- (Player:GetAuras():FindMy(ShadowDance):IsDown() or +-- GetRTBCount() >= 6) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/blade_rush,if=variable.blade_flurry_sync&!buff.dreadblades.up&(energy.base_time_to_max>4+stealthed.rogue-spell_targets%3) +-- CDsAPL:AddSpell( +-- BladeRush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and +-- not Player:GetAuras():FindMy(Dreadblades):IsUp() and +-- (Player:GetTimeToPowerPercent() > 4 + (Player:GetAuras():FindMy(Subterfuge):IsUp() and 1 or 0) + +-- (Player:GetAuras():FindMy(ShadowDance):IsUp() and 1 or 0) - Target:GetMeleeAttackers() % 3) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/call_action_list,name=stealth_cds,if=!stealthed.all|talent.count_the_odds&!variable.stealthed_cto +-- CDsAPL:AddAPL( +-- StealthCDsAPL, +-- function() +-- return not Player:GetAuras():FindMy(Stealth):IsUp() or +-- (CountTheOdds:IsKnown() and not DefaultAPL:GetVariable("stealthed_cto")) +-- end +-- ) + +-- -- actions.cds+=/dreadblades,if=!(variable.stealthed_cto|stealthed.basic|talent.hidden_opportunity&stealthed.rogue)&combo_points<=2&(!talent.marked_for_death|!cooldown.marked_for_death.ready)&target.time_to_die>=10 +-- CDsAPL:AddSpell( +-- Dreadblades:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and +-- not (DefaultAPL:GetVariable("stealthed_cto") or Player:GetAuras():FindMy(Stealth):IsUp() or +-- (HiddenOpportunity:IsKnown() and Player:GetAuras():FindMy(Subterfuge):IsUp())) and +-- Player:GetComboPoints() <= 2 and +-- (not MarkedForDeath:IsKnown() or not MarkedForDeath:CooldownUp()) and +-- Target:TimeToDie() >= 10 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or without any CP. +-- -- actions.cds+=/marked_for_death,line_cd=1.5,target_if=min:target.time_to_die,if=raid_event.adds.up&(target.time_to_die=cp_max_spend-1)&!buff.dreadblades.up + +-- -- # If no adds will die within the next 30s, use MfD on boss without any CP. +-- -- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend-1&!buff.dreadblades.up + +-- -- actions.cds+=/thistle_tea,if=!buff.thistle_tea.up&(energy.base_deficit>=100|fight_remains= 100 or Target:TimeToDie() < ThistleTea:Charges() * 6) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/killing_spree,if=variable.blade_flurry_sync&!stealthed.rogue&debuff.between_the_eyes.up&energy.base_time_to_max>4 +-- CDsAPL:AddSpell( +-- KillingSpree:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable("blade_flurry_sync") and +-- not Player:GetAuras():FindMy(Subterfuge):IsUp() and +-- Target:GetAuras():FindMy(BetweenTheEyes):IsUp() and +-- Player:GetTimeToPowerPercent() > 4 +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/shadowmeld,if=!stealthed.all&(talent.count_the_odds&variable.finish_condition|!talent.weaponmaster.enabled&variable.ambush_condition) +-- CDsAPL:AddSpell( +-- Shadowmeld:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and +-- (CountTheOdds:IsKnown() and DefaultAPL:GetVariable("finish_condition") or +-- not Weaponmaster:IsKnown() and DefaultAPL:GetVariable("ambush_condition")) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.adrenaline_rush.up +-- -- CDsAPL:AddItem( +-- -- PotionOfUnbridledFury:CastableIf( +-- -- function(self) +-- -- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(Bloodlust):IsUp() or Target:TimeToDie() < 30 or +-- -- Player:GetAuras():FindMy(AdrenalineRush):IsUp() +-- -- end +-- -- ):SetTarget(Target) +-- -- ) + +-- -- actions.cds+=/blood_fury +-- CDsAPL:AddSpell( +-- BloodFury:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/berserking +-- CDsAPL:AddSpell( +-- Berserking:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/fireblood +-- CDsAPL:AddSpell( +-- Fireblood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.cds+=/ancestral_call +-- CDsAPL:AddSpell( +-- AncestralCall:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Default conditions for usable items. +-- -- actions.cds+=/use_item,name=manic_grieftorch,if=!stealthed.all&!buff.adrenaline_rush.up|fight_remains<5 +-- -- actions.cds+=/use_item,name=stormeaters_boon,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<10 +-- -- actions.cds+=/use_item,name=windscar_whetstone,if=spell_targets.blade_flurry>desired_targets|raid_event.adds.in>60|fight_remains<7 +-- -- actions.cds+=/use_items,slots=trinket1,if=debuff.between_the_eyes.up|trinket.1.has_stat.any_dps|fight_remains<=20 +-- -- actions.cds+=/use_items,slots=trinket2,if=debuff.between_the_eyes.up|trinket.2.has_stat.any_dps|fight_remains<=20 + +-- -- # Finishers BtE to keep the Crit debuff up, if RP is up, or for Greenskins, unless the target is about to die. +-- -- actions.finish=between_the_eyes,if=target.time_to_die>3&(debuff.between_the_eyes.remains<4|talent.greenskins_wickers&!buff.greenskins_wickers.up|!talent.greenskins_wickers&buff.ruthless_precision.up) +-- FinishAPL:AddSpell( +-- BetweenTheEyes:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Target:TimeToDie() > 3 and +-- (Target:GetAuras():FindMy(BetweenTheEyes):GetRemainingTime() < 4 or +-- GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(GreenskinsWickers):IsDown() or +-- not GreenskinsWickers:IsKnown() and Player:GetAuras():FindMy(RuthlessPrecision):IsUp()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/slice_and_dice,if=buff.slice_and_dice.remains=cp_max_spend) +-- FinishAPL:AddSpell( +-- SliceAndDice:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < Target:TimeToDie() and +-- Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < 6 and +-- (not SwiftSlasher:IsKnown() or Player:GetComboPoints() >= Player:GetComboPointsMax()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/cold_blood +-- FinishAPL:AddSpell( +-- ColdBlood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.finish+=/dispatch +-- FinishAPL:AddSpell( +-- Dispatch:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and true +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Stealth +-- -- actions.stealth=blade_flurry,if=talent.subterfuge&talent.hidden_opportunity&spell_targets>=2&!buff.blade_flurry.up +-- StealthAPL:AddSpell( +-- BladeFlurry:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and Subterfuge:IsKnown() and HiddenOpportunity:IsKnown() and +-- Target:GetMeleeAttackers() >= 2 and +-- not Player:GetAuras():FindMy(BladeFlurry):IsUp() +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/cold_blood,if=variable.finish_condition +-- StealthAPL:AddSpell( +-- ColdBlood:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/dispatch,if=variable.finish_condition +-- StealthAPL:AddSpell( +-- Dispatch:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and DefaultAPL:GetVariable('finish_condition') +-- end +-- ):SetTarget(Target) +-- ) + +-- -- actions.stealth+=/ambush,if=variable.stealthed_cto|stealthed.basic&talent.find_weakness&!debuff.find_weakness.up|talent.hidden_opportunity +-- StealthAPL:AddSpell( +-- Ambush:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and (DefaultAPL:GetVariable('stealthed_cto') or +-- Player:IsStealthed() and FindWeakness:IsKnown() and not Target:GetAuras():FindMy(FindWeakness):IsUp() or +-- HiddenOpportunity:IsKnown()) +-- end +-- ):SetTarget(Target) +-- ) + +-- -- # Stealth Cooldowns +-- -- actions.stealth_cds=variable,name=vanish_condition,value=talent.hidden_opportunity|!talent.shadow_dance|!cooldown.shadow_dance.ready +-- StealthAPL:AddVariable( +-- 'vanish_condition', +-- function() +-- return HiddenOpportunity:IsKnown() or not ShadowDance:IsKnown() or not ShadowDance:CooldownUp() +-- end +-- ) + +-- -- actions.stealth_cds+=/variable,name=vanish_opportunity_condition,value=!talent.shadow_dance&talent.fan_the_hammer.rank+talent.quick_draw+talent.audacity120&(variable.finish_condition|talent.hidden_opportunity)) +-- StealthAPL:AddSpell( +-- ShadowDance:CastableIf( +-- function(self) +-- return self:IsKnownAndUsable() and KeepItRolling:IsKnown() and +-- StealthAPL:GetVariable('shadow_dance_condition') and +-- (KeepItRolling:GetCooldownRemaining() <= 30 or +-- KeepItRolling:GetCooldownRemaining() > 120 and +-- (DefaultAPL:GetVariable('finish_condition') or HiddenOpportunity:IsKnown())) +-- end +-- ):SetTarget(Target) +-- ) + +-- OutlawModule:Sync(function() +-- print(BetweenTheEyes:IsKnownAndUsable()) +-- DefaultAPL:Execute() +-- end) + +-- Bastion:Register(OutlawModule) diff --git a/scripts/restodruid.lua b/scripts/restodruid.lua index c5c2954..272b585 100644 --- a/scripts/restodruid.lua +++ b/scripts/restodruid.lua @@ -111,380 +111,371 @@ local RakeDebuff = Bastion.SpellBook:GetSpell(155722) local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) - local lowest = nil - local lowestHP = math.huge + local lowest = nil + local lowestHP = math.huge - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - local hp = unit:GetHP() - if hp < lowestHP then - lowest = unit - lowestHP = hp - end - end) + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end) - if not lowest then - lowest = Player - end + if not lowest then + lowest = Player + end - return lowest -end) + return lowest + end) local DispelTarget = Bastion.UnitManager:CreateCustomUnit('dispel', function(unit) - local lowest = nil - local lowestHP = math.huge - - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsDead() then - return false - end + local lowest = nil + local lowestHP = math.huge - if not Player:CanSee(unit) then - return false - end + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if not Player:CanSee(unit) then + return false + end - if not unit:IsDead() and Player:CanSee(unit) and - unit:GetAuras():HasAnyDispelableAura(NaturesCure) then + if Player:GetDistance(unit) > 40 then + return false + end - local hp = unit:GetHP() - if hp < lowestHP then - lowest = unit - lowestHP = hp + if not unit:IsDead() and Player:CanSee(unit) and + unit:GetAuras():HasAnyDispelableAura(NaturesCure) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end end + end) + + if lowest == nil then + lowest = None end + + return lowest end) - if lowest == nil then - lowest = None - end +local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) + local purge = nil - return lowest -end) + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end -local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) - local purge = nil + if not Player:CanSee(unit) then + return false + end - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not Player:CanSee(unit) then - return false - end + if not unit:IsDead() and Player:CanSee(unit) and + unit:GetAuras():HasAnyStealableAura() then + purge = unit + return true + end + end) - if Player:GetDistance(unit) > 40 then - return false + if purge == nil then + purge = None end - if not unit:IsDead() and Player:CanSee(unit) and - unit:GetAuras():HasAnyStealableAura() then - purge = unit - return true - end + return purge end) - if purge == nil then - purge = None - end +local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) + local tank = nil - return purge -end) + Bastion.UnitManager:EnumFriends(function(unit) + if Player:GetDistance(unit) > 40 then + return false + end -local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) - local tank = nil + if not Player:CanSee(unit) then + return false + end - Bastion.UnitManager:EnumFriends(function(unit) - if Player:GetDistance(unit) > 40 then - return false - end + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if unit:IsTank() then + tank = unit + return true + end - if unit:IsDead() then return false - end + end) - if unit:IsTank() then - tank = unit - return true + if tank == nil then + tank = Player end - return false + return tank end) - if tank == nil then - tank = Player - end - - return tank -end) - local RejuvUnit = Bastion.UnitManager:CreateCustomUnit('rejuv', function(unit) - local lowest = nil - local lowestHP = math.huge + local lowest = nil + local lowestHP = math.huge - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not unit:IsDead() and Player:CanSee(unit) and - ( - not unit:GetAuras():FindMy(Rejuvenation):IsUp() or + if not unit:IsDead() and Player:CanSee(unit) and + ( + not unit:GetAuras():FindMy(Rejuvenation):IsUp() or unit:GetAuras():FindMy(Rejuvenation):GetRemainingTime() <= 3.6) then - - local hp = unit:GetHP() - if hp < lowestHP then - lowest = unit - lowestHP = hp + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end end - end - - end) + end) - if lowest == nil then - lowest = Player - end + if lowest == nil then + lowest = Player + end - return lowest -end) + return lowest + end) local SwiftmendUnit = Bastion.UnitManager:CreateCustomUnit('swiftmend', function(unit) - local lowest = nil - local lowestHP = math.huge + local lowest = nil + local lowestHP = math.huge - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if ( - Player:CanSee(unit) and ( + if ( + Player:CanSee(unit) and ( (unit:GetAuras():FindMy(Regrowth):IsUp()) - or - ( - unit:GetAuras():FindMy(Rejuvenation):IsUp() and - not unit:GetAuras():FindMy(WildGrowth):IsUp()) + or + ( + unit:GetAuras():FindMy(Rejuvenation):IsUp() and + not unit:GetAuras():FindMy(WildGrowth):IsUp()) ) - ) then - - local hp = unit:GetHP() - if hp < lowestHP then - lowest = unit - lowestHP = hp + ) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end end - end - - end) + end) - if lowest == nil then - lowest = None - end + if lowest == nil then + lowest = None + end - return lowest -end) + return lowest + end) local WildGrowthUnit = Bastion.UnitManager:CreateCustomUnit('wildgrowth', function(unit) - local lowest = nil - local lowestHP = math.huge + local lowest = nil + local lowestHP = math.huge - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if Player:CanSee(unit) and ( - ( + if Player:CanSee(unit) and ( + ( Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and - ( - Player:GetAuras():FindMy(SoulOfTheForest):GetRemainingTime() <= 5 or - unit:GetPartyHPAround(30, 90) >= 2)) or + ( + Player:GetAuras():FindMy(SoulOfTheForest):GetRemainingTime() <= 5 or + unit:GetPartyHPAround(30, 90) >= 2)) or (unit:GetPartyHPAround(30, 90) >= 3 or unit:GetPartyHPAround(30, 85) >= 2)) - then - local hp = unit:GetHP() - if hp < lowestHP then - lowest = unit - lowestHP = hp + then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end end + end) + + + if lowest == nil then + lowest = None end + return lowest end) +local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) + local explosive = nil - if lowest == nil then - lowest = None - end - - return lowest -end) + Bastion.ObjectManager.explosives:each(function(unit) + if unit:IsDead() then + return false + end -local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) - local explosive = nil + if not Player:CanSee(unit) then + return false + end - Bastion.ObjectManager.explosives:each(function(unit) - if unit:IsDead() then - return false - end + if Player:GetDistance(unit) <= 40 then + explosive = unit + return true + end + end) - if not Player:CanSee(unit) then - return false + if explosive == nil then + explosive = None end - if Player:GetDistance(unit) <= 40 then - explosive = unit - return true - end + return explosive end) - if explosive == nil then - explosive = None - end - - return explosive -end) - local RakeTarget = Bastion.UnitManager:CreateCustomUnit('rake', function(unit) - local rakeTarget = nil + local rakeTarget = nil - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and unit:InMelee(Player) and - Player:IsFacing(unit) and - ( - not unit:GetAuras():FindMy(RakeDebuff):IsUp() or + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and unit:InMelee(Player) and + Player:IsFacing(unit) and + ( + not unit:GetAuras():FindMy(RakeDebuff):IsUp() or unit:GetAuras():FindMy(RakeDebuff):GetRemainingTime() <= 3.6) then - rakeTarget = unit - end - - end) + rakeTarget = unit + end + end) - if rakeTarget == nil then - rakeTarget = None - end + if rakeTarget == nil then + rakeTarget = None + end - return rakeTarget -end) + return rakeTarget + end) local MoonfireTarget = Bastion.UnitManager:CreateCustomUnit('moonfire', function(unit) - local moonfireTarget = nil + local moonfireTarget = nil - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and - ( - not unit:GetAuras():FindMy(MoonfireAura):IsUp() or + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and + ( + not unit:GetAuras():FindMy(MoonfireAura):IsUp() or unit:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 3.6) then - moonfireTarget = unit + moonfireTarget = unit + end + end) + + if moonfireTarget == nil then + moonfireTarget = None end + return moonfireTarget end) - if moonfireTarget == nil then - moonfireTarget = None - end - - return moonfireTarget -end) - local SunfireTarget = Bastion.UnitManager:CreateCustomUnit('sunfire', function(unit) - local sunfireTarget = nil + local sunfireTarget = nil - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - if not Player:CanSee(unit) then - return false - end + if not Player:CanSee(unit) then + return false + end - if Player:GetDistance(unit) > 40 then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and - ( - not unit:GetAuras():FindMy(SunfireAura):IsUp() or + if not unit:IsDead() and Player:CanSee(unit) and unit:InCombatOdds() > 80 and + ( + not unit:GetAuras():FindMy(SunfireAura):IsUp() or unit:GetAuras():FindMy(SunfireAura):GetRemainingTime() <= 3.6) then - sunfireTarget = unit + sunfireTarget = unit + end + end) + + if sunfireTarget == nil then + sunfireTarget = None end + return sunfireTarget end) - if sunfireTarget == nil then - sunfireTarget = None - end - - return sunfireTarget -end) - local RestoCommands = Bastion.Command:New('resto') local PLACE_EFFLO = false @@ -502,7 +493,7 @@ DamageAPL:AddSpell( return RakeTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ( not RakeTarget:GetAuras():FindMy(RakeDebuff):IsUp() or - RakeTarget:GetAuras():FindMy(RakeDebuff):GetRemainingTime() <= 3.6) + RakeTarget:GetAuras():FindMy(RakeDebuff):GetRemainingTime() <= 3.6) end):SetTarget(RakeTarget) ) @@ -520,11 +511,6 @@ DamageAPL:AddSpell( end):SetTarget(Target) ) -DefaultAPL:AddSpell( - Moonfire:CastableIf(function(self) - return Explosive:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - end):SetTarget(Explosive) -) DefaultAPL:AddSpell( Efflorescence:CastableIf(function(self) @@ -545,7 +531,7 @@ end) DefaultAPL:AddAction( 'cat_form_shift', function() - if (IsShiftKeyDown() or not Player:IsAffectingCombat()) and not Player:IsMounted() and + if (IsShiftKeyDown()) and not Player:IsMounted() and not Player:GetAuras():FindMy(CatForm):IsUp() and not Player:IsCastingOrChanneling() then CatForm:Cast(Player) @@ -613,9 +599,9 @@ DefaultAPL:AddSpell( and Player:CanSee(SwiftmendUnit) and ( SwiftmendUnit:GetHP() <= 60 or - ( - Lowest:GetPartyHPAround(30, 90) >= 3 or Lowest:GetPartyHPAround(30, 85) >= 2 - ) + ( + Lowest:GetPartyHPAround(30, 90) >= 3 or Lowest:GetPartyHPAround(30, 85) >= 2 + ) ) end):SetTarget(SwiftmendUnit) ) @@ -626,8 +612,8 @@ DefaultAPL:AddSpell( and Player:CanSee(WildGrowthUnit) and ( Player:GetAuras():FindMy(SoulOfTheForest):IsUp() - or - (WildGrowthUnit:GetPartyHPAround(30, 90) >= 3 or WildGrowthUnit:GetPartyHPAround(30, 85) >= 2) + or + (WildGrowthUnit:GetPartyHPAround(30, 90) >= 3 or WildGrowthUnit:GetPartyHPAround(30, 85) >= 2) ) and not Player:IsMoving() end):SetTarget(WildGrowthUnit) @@ -647,7 +633,7 @@ DefaultAPL:AddSpell( and Player:CanSee(Lowest) and Lowest:GetHP() < 70 and ( NaturesSwiftness:GetTimeSinceLastCast() < 2 or Player:GetAuras():FindMy(NaturesSwiftness):IsUp() or - NaturesSwiftness:IsKnownAndUsable()) and not Player:IsMoving() and + NaturesSwiftness:IsKnownAndUsable()) and not Player:IsMoving() and not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() end):SetTarget(Lowest) ) @@ -680,7 +666,7 @@ DefaultAPL:AddSpell( and ( not Player:GetAuras():FindMy(LifebloomAura):IsUp() or - Player:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Player:IsAffectingCombat() + Player:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Player:IsAffectingCombat() end):SetTarget(Player) ) @@ -690,7 +676,7 @@ DefaultAPL:AddSpell( and ( not Tank:GetAuras():FindMy(LifebloomAura):IsUp() or - Tank:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Tank:IsAffectingCombat() + Tank:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Tank:IsAffectingCombat() end):SetTarget(Tank) ) @@ -700,7 +686,7 @@ DefaultAPL:AddSpell( and Player:CanSee(Lowest) and ( not Player:GetAuras():FindMy(Regrowth):IsUp() and Lowest:GetHP() < 70 or - (Lowest:GetHP() <= 85 and Player:GetAuras():FindMy(ClearCasting):IsUp())) and + (Lowest:GetHP() <= 85 and Player:GetAuras():FindMy(ClearCasting):IsUp())) and not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and not Player:IsMoving() end):SetTarget(Lowest) @@ -714,13 +700,20 @@ DefaultAPL:AddSpell( end):SetTarget(RejuvUnit) ) + +DefaultAPL:AddSpell( + Moonfire:CastableIf(function(self) + return Explosive:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + end):SetTarget(Explosive) +) + DefaultAPL:AddSpell( Sunfire:CastableIf(function(self) return SunfireTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:CanSee(SunfireTarget) and ( not SunfireTarget:GetAuras():FindMy(SunfireAura):IsUp() or - SunfireTarget:GetAuras():FindMy(SunfireAura):GetRemainingTime() <= 5.4) and + SunfireTarget:GetAuras():FindMy(SunfireAura):GetRemainingTime() <= 5.4) and SunfireTarget:IsHostile() and SunfireTarget:IsAffectingCombat() and Player:GetPP() >= 25 end):SetTarget(SunfireTarget) @@ -732,7 +725,7 @@ DefaultAPL:AddSpell( and Player:CanSee(MoonfireTarget) and ( not MoonfireTarget:GetAuras():FindMy(MoonfireAura):IsUp() or - MoonfireTarget:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 5.4) and + MoonfireTarget:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 5.4) and MoonfireTarget:IsHostile() and MoonfireTarget:IsAffectingCombat() and Player:GetPP() >= 25 end):SetTarget(MoonfireTarget) diff --git a/scripts/subtlety.lua b/scripts/subtlety.lua index 640c1f5..e5ab155 100644 --- a/scripts/subtlety.lua +++ b/scripts/subtlety.lua @@ -1,754 +1,3 @@ --- 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') @@ -759,1542 +8,1366 @@ 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 GrandMelee = Bastion.SpellBook:GetSpell(193358) -local Broadside = Bastion.SpellBook:GetSpell(193356) -local TrueBearing = Bastion.SpellBook:GetSpell(193359) -local RuthlessPrecision = Bastion.SpellBook:GetSpell(193357) -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 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 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 DarkMoonRime = Bastion.ItemBook:GetItem(198477) -local AlgetharsPuzzleBox = Bastion.ItemBook:GetItem(193701) - -local RimeCards = { - 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), - Ace = Bastion.SpellBook:GetSpell(382844), -} +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 PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) - local purge = nil +local DeadgeHealth = 120000 * 12 - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end - - if not Player:CanSee(unit) then - return false - end +local function IsDeadge(unit) return false end - if Player:GetDistance(unit) > 40 then - return false - end +local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) + local purge = nil - 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) + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end -local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) - local kick = nil + if not Player:CanSee(unit) then + return false + end - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not Player:CanSee(unit) then - return false - end + if unit:GetAuras():HasAnyStealableAura() and Shiv:IsInRange(unit) then + purge = unit + return true + end + end) - if Player:GetDistance(unit) > 40 then - return false + if purge == nil then + purge = None end - if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalKick(unit, 5) then - kick = unit - return true - end + return purge end) - if kick == nil then - kick = None - end - - return kick -end) +local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit) + local kick = nil -local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) - local tank = nil + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - Bastion.UnitManager:EnumFriends(function(unit) - if Player:GetDistance(unit) > 40 then - return false - end + if not Player:CanSee(unit) then + return false + end - if not Player:CanSee(unit) then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if unit:IsDead() 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 unit:IsTank() then - tank = unit - return true + if kick == nil then + kick = None end - return false + return kick end) - if tank == nil then - tank = None - end - - return tank -end) +local StunTarget = Bastion.UnitManager:CreateCustomUnit('stun', function(unit) + local stun = nil -local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function() - local target = nil + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + if not Player:CanSee(unit) then + return false + end - if not Player:CanSee(unit) then - return false - end + if Player:GetDistance(unit) > 40 then + return false + end - if not Player:InMelee(unit) 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 not Player:IsFacing(unit) then - return false + if stun == nil then + stun = None 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 + return stun end) - if target == nil then - target = None - end +local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) + local tank = nil - return target -end) - -local function GetRimeDuration(topCard) - if topCard == RimeCards.Two then - return 0 - elseif topCard == RimeCards.Three then - return 1 - elseif topCard == RimeCards.Four then - return 2 - elseif topCard == RimeCards.Five then - return 3 - elseif topCard == RimeCards.Six then - return 4 - elseif topCard == RimeCards.Seven then - return 5 - elseif topCard == RimeCards.Eight then - return 6 - elseif topCard == RimeCards.Ace then - return 7 - end - - return 0 -end - -local function GetRimeTopCard() - local topCard = nil - - if Player:GetAuras():FindMy(RimeCards.Ace):IsUp() then - topCard = RimeCards.Ace - elseif Player:GetAuras():FindMy(RimeCards.Two):IsUp() then - topCard = RimeCards.Two - elseif Player:GetAuras():FindMy(RimeCards.Three):IsUp() then - topCard = RimeCards.Three - elseif Player:GetAuras():FindMy(RimeCards.Four):IsUp() then - topCard = RimeCards.Four - elseif Player:GetAuras():FindMy(RimeCards.Five):IsUp() then - topCard = RimeCards.Five - elseif Player:GetAuras():FindMy(RimeCards.Six):IsUp() then - topCard = RimeCards.Six - elseif Player:GetAuras():FindMy(RimeCards.Seven):IsUp() then - topCard = RimeCards.Seven - elseif Player:GetAuras():FindMy(RimeCards.Eight):IsUp() then - topCard = RimeCards.Eight - end - - return topCard -end - -local function HasRimeCard() - return GetRimeTopCard() ~= nil -end - -local RimeTarget = Bastion.UnitManager:CreateCustomUnit('rime', function() - local target = nil + Bastion.UnitManager:EnumFriends(function(unit) + if Player:GetDistance(unit) > 40 then + return false + end - Bastion.UnitManager:EnumEnemies(function(unit) - if unit:IsDead() then - return false - end + if not Player:CanSee(unit) then + return false + end - if not Player:CanSee(unit) then - return false - end + if unit:IsDead() then + return false + end - if not Player:InMelee(unit) then - return false - end + if unit:IsTank() then + tank = unit + return true + end - if not Player:IsFacing(unit) then return false - end + end) - if not unit:IsBoss() and Player:GetEnemies(10) < 3 then - return false + if tank == nil then + tank = None end - if HasRimeCard() and DarkMoonRime:IsEquippedAndUsable() and - (unit:TimeToDie() < GetRimeDuration(GetRimeTopCard()) - 0.5 or unit:IsBoss()) then - target = unit - return true - end + return tank end) - if target == nil then - target = None - end - - return target -end) - -local DefaultAPL = Bastion.APL:New('default') -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( - Healthstone:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 40 - end):SetTarget(Player) -) - -ItemsAPL:AddItem( - RefreshingHealingPotion:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 40 - end):SetTarget(Player) -) - -ItemsAPL: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) -) - -ItemsAPL:AddSpell( - Evasion:CastableIf(function(self) - return self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and - Player:GetHealthPercent() < 40 - end):SetTarget(Player) -) - -ItemsAPL:AddItem( - IrideusFragment:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) - end):SetTarget(Player) -) - -ItemsAPL:AddItem( - ElementalPotionOfPower:UsableIf(function(self) - return self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) - end):SetTarget(Player) -) - -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) -) - -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()) and - not Player:IsMoving() - end):SetTarget(Player) -) - --- Since we are sniping soon to die targets with rime we can put it in the default APL since we want burst damage -DefaultAPL:AddItem( - DarkMoonRime:UsableIf(function(self) - return RimeTarget:Exists() and Player:InMelee(RimeTarget) and self:IsEquippedAndUsable() and - not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) and - HasRimeCard() - end):SetTarget(RimeTarget) -) - --- # Executed every time the actor is available. --- # Restealth if possible (no vulnerable enemies in combat) --- actions=stealth -DefaultAPL:AddSpell( - Stealth:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and - not Player:IsAffectingCombat() and not IsMounted() - end - ):SetTarget(Player) -) - --- # Interrupt on cooldown to allow simming interactions with that --- actions+=/kick -DefaultAPL:AddSpell( - Kick:CastableIf(function(self) - return KickTarget:Exists() and self:IsInRange(KickTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:IsFacing(Target) - end):SetTarget(KickTarget) -) - -DefaultAPL:AddSpell( - KidneyShot:CastableIf(function(self) - return KickTarget:Exists() and self:IsInRange(KickTarget) and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:IsFacing(Target) - end):SetTarget(KickTarget) -) - --- Purge -DefaultAPL:AddSpell( - Shiv:CastableIf(function(self) - return PurgeTarget:Exists() and - self:IsKnownAndUsable() and - not Player:IsCastingOrChanneling() and Player:IsFacing(Target) - end):SetTarget(PurgeTarget) -) - --- 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) >= ConsumeCPMax() - end -) +local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function() + local target = nil --- # 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 + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then + return false + end - local comboPoints = Player:GetComboPoints() + if not Player:CanSee(unit) then + return false + end - if comboPoints == 1 and Player:GetAuras():FindMy(EchoingReprimand2):IsUp() then - return true - end + if not Player:InMelee(unit) then + return false + end - if comboPoints == 2 and Player:GetAuras():FindMy(EchoingReprimand3):IsUp() then - return true - end + if not Player:IsFacing(unit) then + return false + end - if comboPoints == 3 and Player:GetAuras():FindMy(EchoingReprimand4):IsUp() then - return true - 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 comboPoints == 4 and Player:GetAuras():FindMy(EchoingReprimand5):IsUp() then - return true + if target == nil then + target = None end - return false - end -) - --- # 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 --- ) - --- -- 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 --- ) - -DefaultAPL:AddVariable( - 'effective_combo_points', - function() - local cur = Player:GetComboPoints() or 0 - if not EchoingReprimand:IsKnown() then - return cur - end + return target + end) - if cur < 2 or cur > 5 then - return cur - 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 - 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 +function WasInterruptUsedWithin(time) + local timeSinceKick = Kick:GetTimeSinceLastCastAttempt() + local timeSinceKidney = KidneyShot:GetTimeSinceLastCastAttempt() + local timeSinceGouge = Gouge:GetTimeSinceLastCastAttempt() + local timeSinceBlind = Blind:GetTimeSinceLastCastAttempt() - if not EchoingReprimand:IsKnown() then - return 0 - end + return timeSinceKick < time or timeSinceKidney < time or timeSinceGouge < time or timeSinceBlind < time +end - local cur = Player:GetComboPoints() or 0 - local deficit = Player:GetComboPointsDeficit() or 0 +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, +} - 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 +local lastFeintTime = 0 - -- return 0 - return cur - end -) --- # Check CDs at first --- actions+=/call_action_list,name=cds -DefaultAPL:AddAPL( - CDsAPL, - function() - return true +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) --- # 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) < ConsumeCPMax() and - Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() < Player:GetGCD() and - Player:GetAuras():FindMy(SliceAndDice):GetRemainingTime() > 6 and - Player:GetComboPoints() >= 4 - end - ):SetTarget(Player) +-- 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) ) --- # 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 +DefensivesAPL:AddSpell( + CrimsonVial:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 70 and + not WasSelfHealUsedWithin(5) + 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 +DefensivesAPL:AddItem( + Healthstone:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 40 and + not WasSelfHealUsedWithin(5) + 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 +DefensivesAPL:AddItem( + RefreshingHealingPotion:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and + Player:GetHealthPercent() < 40 and + not WasSelfHealUsedWithin(5) + 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 +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) ) --- actions+=/call_action_list,name=finish,if=variable.effective_combo_points>=cp_max_spend -DefaultAPL:AddAPL( - FinishAPL, - function() - return DefaultAPL:GetVariable('effective_combo_points') >= ConsumeCPMax() - end -) +-- DefensivesAPL:AddSpell( +-- Evasion:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and +-- Player:GetHealthPercent() < 40 +-- end):SetTarget(Player) +-- ) --- # 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 +TrinketsAPL:AddItem( + IrideusFragment:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) ) --- # 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 +TrinketsAPL:AddItem( + ElementalPotionOfPower:UsableIf(function(self) + return self:IsEquippedAndUsable() and + not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss()) + end):SetTarget(Player) ) --- # 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 +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) ) --- # Lowest priority in all of the APL because it causes a GCD --- actions+=/arcane_torrent,if=energy.deficit>=15+energy.regen +-- 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( - ArcaneTorrent:CastableIf( + Stealth:CastableIf( function(self) - return self:IsKnownAndUsable() and Player:InMelee(Target) and - Player:GetPowerDeficit() >= 15 + Player:GetPowerRegen() + return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and + not Player:IsAffectingCombat() and not IsMounted() end ):SetTarget(Player) ) --- actions+=/arcane_pulse DefaultAPL:AddSpell( - ArcanePulse:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and true - end - ):SetTarget(Target) + 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) ) --- actions+=/lights_judgment DefaultAPL:AddSpell( - LightsJudgment:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and true - end - ):SetTarget(Target) + 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) ) --- actions+=/bag_of_tricks DefaultAPL:AddSpell( - BagOfTricks:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and true - end - ):SetTarget(Target) + 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) ) --- # Builders --- actions.build=shuriken_storm,if=spell_targets>=2+(talent.gloomblade&buff.lingering_shadow.remains>=6|buff.perforated_veins.up) -BuildAPL:AddSpell( - ShurikenStorm:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and - Player:GetEnemies(10) >= 2 + ((Gloomblade:IsKnown() and - (Player:GetAuras():FindMy(LingeringShadow):GetRemainingTime() >= 6 or - Player:GetAuras():FindMy(PerforatedVeins):IsUp())) and 1 or 0) - end - ):SetTarget(Target) +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) +-- ) --- 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 +-- 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) +) - if n <= swings_since_sht then return 0 end +--[[ + 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 mh_speed = swings.mainhand_speed - local mh_next = ( swings.mainhand > now - 3 ) and ( swings.mainhand + mh_speed ) or now + ( mh_speed * 0.5 ) +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 oh_speed = swings.offhand_speed - local oh_next = ( swings.offhand > now - 3 ) and ( swings.offhand + oh_speed ) or now +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 - table.wipe( sht ) +local function OpenerAbortCondition(spell) + -- If the cooldown is over 2 seconds we should abort the opener + return spell:GetCooldownRemaining() > 2 +end - if mh_speed and mh_speed > 0 then - for i = 1, 4 do - insert( sht, mh_next + ( i * mh_speed ) ) +local OpenerSequenceST = Bastion.Sequencer:New({ + function(self) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true end - end - if oh_speed and oh_speed > 0 then - for i = 1, 4 do - insert( sht, oh_next + ( i * oh_speed ) ) + 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 - end - - local i = 1 - while( sht[i] ) do - if sht[i] < last_shadow_techniques + 3 then - table.remove( sht, i ) - else - i = i + 1 + 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 - 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 GetTimeToShurikenTornado(unit, n) - local now = GetTime() - local sht = {} - local swings = unit:GetSwingTimers() + 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 not unit.swings_since_sht then - unit.swings_since_sht = 0 - 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 not unit.last_shadow_techniques then - unit.last_shadow_techniques = 0 - 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 n <= unit.swings_since_sht then - return 0 - 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 - local mh_speed = swings[1] - local mh_next = (unit.last_mh > now - 3) and (unit.last_mh + mh_speed) or now + (mh_speed * 0.5) + 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 - local oh_speed = swings[2] - local oh_next = (unit.last_oh > now - 3) and (unit.last_oh + oh_speed) or now + 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 - table.wipe(sht) + 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 mh_speed and mh_speed > 0 then - for i = 1, 4 do - table.insert(sht, mh_next + (i * mh_speed)) - end - 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 oh_speed and oh_speed > 0 then - for i = 1, 4 do - table.insert(sht, oh_next + (i * oh_speed)) - end - 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 - local i = 1 + 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 - while (sht[i]) do - if sht[i] < unit.last_shadow_techniques + 3 then - table.remove(sht, i) - else - i = i + 1 - end - end + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, + }, ShouldResetSTOpener) - if #sht > 0 and n - unit.swings_since_sht < #sht then - table.sort(sht) - return math.max(0, sht[n - unit.swings_since_sht] - now) - else - return 3600 - end +--[[ + 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 --- # 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 - (GetTimeToShurikenTornado(Player, 3) < 0.5 or GetTimeToShurikenTornado(Player, 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) -) - --- 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) -) - --- # 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) -) +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 --- # (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 Player:IsAffectingCombat() and self:IsKnownAndUsable() and - Player:GetAuras():FindMy(ShurikenTornado):IsUp() and - Player:GetAuras():FindMy(ShurikenTornado):GetRemainingTime() <= 3.5 - end - ):SetTarget(Player) -) +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 --- # 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( +local OpenerSequenceAOE = Bastion.Sequencer:New({ 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 and Vanish:GetTimeSinceLastCast() > 2 - end - ):SetTarget(Player) -) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end --- # 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( + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, function(self) - return self:IsKnownAndUsable() and not SecretTechnique:IsKnown() and - DefaultAPL:GetVariable('effective_combo_points') >= 5 - end - ):SetTarget(Player) -) + if OpenerAbortCondition(SymbolsOfDeath) then + self:Abort() + print("Aborting on SymbolsOfDeath") + return true + end --- 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( + if SymbolsOfDeath:IsKnownAndUsable() then + SymbolsOfDeath:ForceCast(Player) + return true + end + return false + end, 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) -) - --- # 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 - + if OpenerAbortCondition(ShadowBlades) then + self:Abort() + print("Aborting on ShadowBlades") + return true + end --- # 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( + if ShadowBlades:IsKnownAndUsable() then + ShadowBlades:ForceCast(Player) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(ShurikenTornado) then + self:Abort() + print("Aborting on ShurikenTornado") + return true + end --- actions.cds+=/sepsis,if=variable.snd_condition&combo_points.deficit>=1&target.time_to_die>=16 -CDsAPL:AddSpell( - Sepsis:CastableIf( + if ShurikenTornado:IsKnownAndUsable() then + ShurikenTornado:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(ShadowDance) then + self:Abort() + print("Aborting on ShadowDance") + return true + 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( + if ShadowDance:IsKnownAndUsable() then + ShadowDance:ForceCast(Player) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(ThistleTea) then + self:Abort() + print("Aborting on ThistleTea") + return true + end --- # 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 + 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 --- 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( + if Rupture:IsKnownAndUsable() then + Rupture:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + 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( + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(SecretTechnique) then + self:Abort() + print("Aborting on SecretTechnique") + print("Aborting on ColdBlood") + return true + end --- # 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( + if SecretTechnique:IsKnownAndUsable() then + SecretTechnique:ForceCast(Target) + ColdBlood:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + end --- actions.cds+=/shuriken_tornado,if=cooldown.shadow_dance.ready&!stealthed.all&spell_targets.shuriken_storm>=3&!talent.flagellation.enabled -CDsAPL:AddSpell( - ShurikenTornado:CastableIf( + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(Shadowstrike) then + self:Abort() + print("Aborting on Shadowstrike") + return true + end --- actions.cds+=/shadow_dance,if=!buff.shadow_dance.up&fight_remains<=8+talent.subterfuge.enabled -CDsAPL:AddSpell( - ShadowDance:CastableIf( + if Shadowstrike:IsKnownAndUsable() then + Shadowstrike:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(BlackPowder) then + self:Abort() + print("Aborting on BlackPowder") + return true + end --- 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( + if BlackPowder:IsKnownAndUsable() then + BlackPowder:ForceCast(Target) + return true + end + return false + end, 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) -) + if OpenerAbortCondition(ShurikenStorm) then + self:Abort() + print("Aborting on ShurikenStorm") + return true + end --- 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 -) + if ShurikenStorm:IsKnownAndUsable() then + ShurikenStorm:ForceCast(Target) + return true + end + return false + end, + }, ShouldResetAOEOpener) --- # 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 -) +--[[ + == Subtlety Rogue Concepts == --- return ( cp + 1 ) * p()->buffs.slice_and_dice->data().duration(); -function GetTriggeredDuration() - return (Player:GetComboPoints() + 1) * 6 -end + 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 --- return p()->buffs.slice_and_dice->remains() < get_triggered_duration( as( p()->current_effective_cp( false ) ) ) * 0.3; -function Refreshable(aura, target) - return target:GetAuras():FindMy(aura):GetRemainingTime() < GetTriggeredDuration() * 0.3 -end +-- 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) --- actions.finish+=/slice_and_dice,if=!variable.premed_snd_condition&spell_targets.shuriken_storm<6&!buff.shadow_dance.up&buff.slice_and_dice.remains 3 then return false end --- actions.finish+=/slice_and_dice,if=variable.premed_snd_condition&cooldown.shadow_dance.charges_fractional<1.75&buff.slice_and_dice.remains 80 then return false end --- actions.finish+=/variable,name=skip_rupture,value=buff.thistle_tea.up&spell_targets.shuriken_storm=1|buff.shadow_dance.up&(spell_targets.shuriken_storm=1|dot.rupture.ticking&spell_targets.shuriken_storm>=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)) + if SD < 3 and power < 80 then + print("Whys this true") + return true 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 - Refreshable(Rupture, Target) - end - ):SetTarget(Target) -) + return false +end --- # 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) +-- 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 and self:IsInRange(Target) + and not ShouldPoolForCD() + 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) -) +-- BuildersAPL:AddSpell( +-- Backstab:CastableIf(function(self) +-- return self:IsKnownAndUsable() and +-- not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() +-- 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) -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) +-- 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) ) --- # 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 - Refreshable(Rupture, RuptureTarget) - end - ):SetTarget(RuptureTarget) +-- 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. --- # 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) +]] +-- 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) ) --- 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) +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) ) --- actions.finish+=/eviscerate -FinishAPL:AddSpell( - Eviscerate:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and true - end - ):SetTarget(Target) -) +-- 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) +-- ) --- # 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 +-- 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) ) --- # 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 and Vanish:GetTimeSinceLastCast() > 2 - 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) ) --- # 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) +-- 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) ) --- # 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 --- ) - -StealthCDsAPL:AddVariable( - 'shd_combo_points', - function(self) - return (Player:GetComboPoints() <= 1) or (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))) or (Player:GetEnemies(10) == (4 - (SealFate:IsKnown() and 1 or 0))) - 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) ) --- # 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() +-- 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 - ):SetTarget(Target) + end) ) --- # 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 1 + end):SetTarget(Player) ) --- # Variable to Gloomblade / Backstab when on 4 or 5 combo points with premediation and when the combo point is not anima charged --- actions.stealthed+=/variable,name=gloomblade_condition,value=buff.danse_macabre.stack<5&(combo_points.deficit=2|combo_points.deficit=3)&(buff.premeditation.up|effective_combo_points<7)&(spell_targets.shuriken_storm<=8|talent.lingering_shadow) -StealthedAPL:AddVariable( - 'gloomblade_condition', - function(self) - return Player:GetAuras():FindMy(DanseMacabre):GetCount() < 5 and - (Player:GetComboPointsDeficit() == 2 or - Player:GetComboPointsDeficit() == 3) and - (Player:GetAuras():FindMy(Premeditation):IsUp() or - DefaultAPL:GetVariable('effective_combo_points') < 7) and - (Player:GetEnemies(10) <= 8 or - LingeringShadow:IsKnown()) +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) ) --- actions.stealthed+=/shuriken_storm,if=variable.gloomblade_condition&buff.silent_storm.up&!debuff.find_weakness.remains&talent.improved_shuriken_storm.enabled -StealthedAPL:AddSpell( - ShurikenStorm:CastableIf( - function(self) - return self:IsKnownAndUsable() and StealthedAPL:GetVariable('gloomblade_condition') and - Player:GetAuras():FindMy(SilentStorm):IsUp() and - not Target:GetAuras():FindMy(FindWeakness):GetRemainingTime() and - ImprovedShurikenStorm:IsKnown() - 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) ) --- actions.stealthed+=/gloomblade,if=variable.gloomblade_condition -StealthedAPL:AddSpell( - Gloomblade:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and - StealthedAPL:GetVariable('gloomblade_condition') - 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 + 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) ) --- actions.stealthed+=/backstab,if=variable.gloomblade_condition&talent.danse_macabre&buff.danse_macabre.stack<=2&spell_targets.shuriken_storm<=2 -StealthedAPL:AddSpell( - Backstab:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) and - StealthedAPL:GetVariable('gloomblade_condition') and - DanseMacabre:IsKnown() and - Player:GetAuras():FindMy(DanseMacabre):GetCount() <= 2 and - Player:GetEnemies(10) <= 2 - end - ):SetTarget(Target) +-- 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) ) ---[[ - 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() ); - } -]] +-- DefaultAPL:AddSequence( +-- OpenerSequenceST, +-- ShouldPerformSTOpener +-- ) -function ConsumeCPMax() - return 5 + (DeeperStratagem:IsKnownAndUsable() and 1 or 0) + (DeviousStratagem:IsKnownAndUsable() and 1 or 0) + - (SecretStratagem:IsKnownAndUsable() and 1 or 0) -end +-- DefaultAPL:AddSequence( +-- OpenerSequenceAOE, +-- ShouldPerformAOEOpener +-- ) --- actions.stealthed+=/call_action_list,name=finish,if=variable.effective_combo_points>=cp_max_spend -StealthedAPL:AddAPL( - FinishAPL, - function(self) - return DefaultAPL:GetVariable('effective_combo_points') >= ConsumeCPMax() - end -) +-- DefaultAPL:AddAPL( +-- OpenersAPL, +-- function() +-- return ShouldPerformSTOpener() or ShouldPerformAOEOpener() +-- 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 +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) ) --- # 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, +DefaultAPL:AddAPL( + DefensivesAPL, function() - return Player:GetEnemies(10) >= 4 - ((SealFate:IsKnown() and - DefaultAPL:GetVariable('effective_combo_points') >= 4) and 1 or 0) + return true 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, +DefaultAPL:AddAPL( + ItemsAPL, function() - return Player:GetComboPointsDeficit() <= - 1 + ((SealFate:IsKnown() or DeeperStratagem:IsKnown() or SecretStratagem:IsKnown()) and 1 or 0) + return true 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) +DefaultAPL:AddAPL( + TrinketsAPL, + function() + return Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and not IsDeadge(Target) + end ) --- 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) +DefaultAPL:AddAPL( + CDsAPL, + function() -- trying out a way to hold CDs, maybe it's dog + return Player:IsAffectingCombat() and not IsShiftKeyDown() + end ) --- # 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= 6 or + (Player:GetComboPoints() >= 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) + end ) --- actions.stealthed+=/shadowstrike -StealthedAPL:AddSpell( - Shadowstrike:CastableIf( - function(self) - return self:IsKnownAndUsable() and self:IsInRange(Target) - end - ):SetTarget(Target) +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) ) -Stealth:CastableIf( - function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and - not Player:IsAffectingCombat() and not IsMounted() +-- 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) diff --git a/src/APL/APL.lua b/src/APL/APL.lua index 3ea1c55..fad2bf7 100644 --- a/src/APL/APL.lua +++ b/src/APL/APL.lua @@ -82,6 +82,25 @@ end -- Execute function APLActor:Execute() + -- If the actor is a sequencer we don't want to continue executing the APL if the sequencer is not finished + if self:GetActor().sequencer then + if self:GetActor().condition and self:GetActor().condition() and not self:GetActor().sequencer:Finished() then + print("Execute?") + self:GetActor().sequencer:Execute() + return true + end + + if not self:GetActor().condition and not self:GetActor().sequencer:Finished() then + print("Execute?") + self:GetActor().sequencer:Execute() + return true + end + + -- Check if the sequencer can be reset and reset it if it can + if self:GetActor().sequencer:ShouldReset() then + self:GetActor().sequencer:Reset() + end + end if self:GetActor().apl then if self:GetActor().condition and self:GetActor().condition() then -- print("Bastion: APL:Execute: Executing sub APL " .. self:GetActor().apl.name) @@ -116,6 +135,7 @@ function APLActor:Execute() -- print("Bastion: APL:Execute: Setting variable " .. self:GetActor().variable) self:GetActor()._apl.variables[self:GetActor().variable] = self:GetActor().cb(self:GetActor()._apl) end + return false end -- has traits @@ -229,13 +249,29 @@ end function APL:Execute() for _, actor in ipairs(self.apl) do if actor:HasTraits() and actor:Evaluate() then - actor:Execute() + if actor:Execute() then + print("BREAQK", actor) + break + end else - actor:Execute() + if actor:Execute() then + print("BREAQK", actor) + break + end end end end +-- Add a Sequencer to the APL +---@param sequencer Sequencer +---@param condition fun(...):boolean +---@return APLActor +function APL:AddSequence(sequencer, condition) + local actor = APLActor:New({ sequencer = sequencer, condition = condition }) + table.insert(self.apl, actor) + return actor +end + -- tostring ---@return string function APL:__tostring() diff --git a/src/MythicPlusUtils/MythicPlusUtils.lua b/src/MythicPlusUtils/MythicPlusUtils.lua index 7e0ee3c..27d4b52 100644 --- a/src/MythicPlusUtils/MythicPlusUtils.lua +++ b/src/MythicPlusUtils/MythicPlusUtils.lua @@ -18,79 +18,365 @@ function MythicPlusUtils:New() self.random = math.random(1000000, 9999999) self.kickList = { - -- Algeth'ar Academy - [396812] = { true, true }, -- https://www.wowhead.com/spell=396812/mystic-blast - [388392] = { true, true }, -- https://www.wowhead.com/spell=388392/monotonous-lecture - [388863] = { true, true }, -- https://www.wowhead.com/spell=388863/mana-void - [388862] = { true, true }, -- https://www.wowhead.com/spell=388862/surge - [377389] = { true, true }, -- https://www.wowhead.com/spell=377389/call-of-the-flock - [388623] = { true, true }, -- https://www.wowhead.com/spell=388623/branch-out - [396640] = { true, true }, -- https://www.wowhead.com/spell=396640/healing-touch - [387975] = { true, true }, -- https://www.wowhead.com/spell=387975/arcane-missiles - [387843] = { true, true }, -- https://www.wowhead.com/spell=387843/astral-bomb - -- Court of Stars - [211401] = { true, true }, -- https://www.wowhead.com/spell=211401/drifting-embers - [207980] = { true, true }, -- https://www.wowhead.com/spell=207980/disintegration-beam - [208165] = { true, true }, -- https://www.wowhead.com/spell=208165/withering-soul - [207881] = { true, true }, -- https://www.wowhead.com/spell=207881/infernal-eruption - [209413] = { true, false }, -- https://www.wowhead.com/spell=209413/suppress - [209485] = { true, true }, -- https://www.wowhead.com/spell=209485/drain-magic - [209410] = { true, true }, -- https://www.wowhead.com/spell=209410/nightfall-orb - [211470] = { true, true }, -- https://www.wowhead.com/spell=211470/bewitch - [225100] = { true, false }, -- https://www.wowhead.com/spell=225100/charging-station - [211299] = { true, false }, -- https://www.wowhead.com/spell=211299/searing-glare - -- Halls of Valor - [198595] = { true, true }, -- https://www.wowhead.com/spell=198595/thunderous-bolt - [198959] = { true, true }, -- https://www.wowhead.com/spell=198959/etch - [215433] = { true, true }, -- https://www.wowhead.com/spell=215433/holy-radiance - [192288] = { true, true }, -- https://www.wowhead.com/spell=192288/searing-light - [199726] = { true, true }, -- https://www.wowhead.com/spell=199726/unruly-yell - [198750] = { true, true }, -- https://www.wowhead.com/spell=198750/surge - [198934] = { true, true }, -- https://www.wowhead.com/spell=198934/rune-of-healing - [192563] = { true, true }, -- https://www.wowhead.com/spell=192563/cleansing-flames - -- Ruby Life Pools - [372749] = { true, true }, -- https://www.wowhead.com/spell=372749/ice-shield - -- [373803] = { true, true }, -- https://www.wowhead.com/spell=373803/cold-claws - [373017] = { true, true }, -- https://www.wowhead.com/spell=373017/roaring-blaze - [392398] = { true, true }, -- https://www.wowhead.com/spell=392398/crackling-detonation - [392451] = { true, true }, -- https://www.wowhead.com/spell=392451/flashfire - [385310] = { true, true }, -- https://www.wowhead.com/spell=385310/lightning-bolt - [384194] = { true, true }, -- https://www.wowhead.com/spell=384194/cinderbolt - [373680] = { true, false }, -- https://www.wowhead.com/spell=373680/frost-overload - -- Shadowmoon Burial Grounds - [152818] = { true, true }, -- https://www.wowhead.com/spell=152818/shadow-mend - [156776] = { true, true }, -- https://www.wowhead.com/spell=156776/rending-voidlash - [156722] = { true, true }, -- https://www.wowhead.com/spell=156722/void-bolt - [398206] = { true, true }, -- https://www.wowhead.com/spell=398206/death-blast - [153524] = { true, true }, -- https://www.wowhead.com/spell=153524/plague-spit - [156718] = { true, true }, -- https://www.wowhead.com/spell=156718/necrotic-burst - -- Temple of the Jade Serpent - [397888] = { true, false }, -- https://www.wowhead.com/spell=397888/hydrolance - [395859] = { true, true }, -- https://www.wowhead.com/spell=395859/haunting-scream - [396073] = { true, true }, -- https://www.wowhead.com/spell=396073/cat-nap - [397914] = { true, true }, -- https://www.wowhead.com/spell=397914/defiling-mist - [397899] = { false, true }, -- https://www.wowhead.com/spell=397899/leg-sweep - [397801] = { true, false }, -- https://www.wowhead.com/spell=397801/hydrolance - [395872] = { true, true }, -- https://www.wowhead.com/spell=395872/sleepy-soliloquy - -- The Azure Vault - [375602] = { true, true }, -- https://www.wowhead.com/spell=375602/erratic-growth - [387564] = { true, true }, -- https://www.wowhead.com/spell=387564/mystic-vapors - [373932] = { true, true }, -- https://www.wowhead.com/spell=373932/illusionary-bolt - [386546] = { true, true }, -- https://www.wowhead.com/spell=386546/waking-bane - [389804] = { true, true }, -- https://www.wowhead.com/spell=389804/heavy-tome - [377488] = { true, true }, -- https://www.wowhead.com/spell=377488/icy-bindings - [377503] = { true, true }, -- https://www.wowhead.com/spell=377503/condensed-frost - [375596] = { true, true }, -- https://www.wowhead.com/spell=375596/erratic-growth - -- NO - [384365] = { true, true }, -- https://www.wowhead.com/spell=384365/disruptive-shout - [386012] = { true, false }, -- https://www.wowhead.com/spell=386012/stormbolt - [386024] = { true, true }, -- https://www.wowhead.com/spell=386024/tempest - [387411] = { true, true }, -- https://www.wowhead.com/spell=387411/death-bolt-volley - [387606] = { true, true }, -- https://www.wowhead.com/spell=387606/dominate - [373395] = { true, true }, -- https://www.wowhead.com/spell=373395/bloodcurdling-shout - [376725] = { true, true }, -- https://www.wowhead.com/spell=376725/storm-bolt - [384808] = { true, true }, -- https://www.wowhead.com/spell=384808/guardian-wind - -- [363607] = {true, true}, -- https://www.wowhead.com/spell=363607/domination-bolt + -- Ruby life pools + [372735] = { -- Techtonic Slam + [187969] = { + false, true, true -- Kick, Stun, Disorient + } + }, + [384933] = { -- Ice Shield + [188067] = { + true, true, true + } + }, + [372749] = { -- Ice Shield + [188067] = { + true, true, true + } + }, + [372743] = { -- Ice Shield + [188067] = { + true, true, true + } + }, + [371984] = { + [188067] = { + true, true, true + } + }, + [373680] = { + [188252] = { + true, false, false + } + }, + [373688] = { + [188252] = { + true, false, false + } + }, + [385310] = { + [195119] = { + true, false, false + } + }, + [384194] = { + [190207] = { + true, true, true + } + }, + [384197] = { + [190207] = { + true, true, true + } + }, + [373017] = { + [189886] = { + true, false, false + } + }, + [392576] = { + [198047] = { + true, false, false + } + }, + [392451] = { + [197985] = { + true, true, false, + } + }, + [392452] = { + [197985] = { + true, true, false, + } + }, + -- Nokhud + [383823] = { + [192796] = { + false, true, true + } + }, + [384492] = { + [192794] = { + false, true, true + } + }, + [384365] = { + [192800] = { + true, false, false + }, + [191847] = { + true, false, false + } + }, + [386012] = { + [194317] = { + true, false, false + }, + [195265] = { + true, false, false + }, + [194315] = { + true, false, false + }, + [194316] = { + true, false, false + } + + }, + [386028] = { + [195696] = { + true, false, false + } + }, + [386024] = { + [194894] = { + true, true, true + } + }, + [386025] = { + [194894] = { + true, true, true + } + }, + [387629] = { + [195876] = { + false, true, true + } + }, + [387608] = { + [195842] = { + false, true, true + } + }, + [387611] = { + [195842] = { + false, true, true + } + }, + [387440] = { + [195878] = { + false, true, true + } + }, + [373395] = { + [199717] = { + true, false, false + } + }, + [376725] = { + [190294] = { + true, true, true + }, + }, + [370764] = { + [187160] = { + false, true, true + }, + [196116] = { + false, true, true + }, + }, + [387564] = { + [196102] = { + true, true, true + } + }, + [375596] = { + [196115] = { + true, false, false + }, + [191164] = { + true, false, false + }, + + }, + [386549] = { + [186741] = { + true, true, true + } + }, + [386546] = { + [186741] = { + true, true, true + } + }, + [389804] = { + [187154] = { + true, false, false + } + }, + [377488] = { + [187155] = { + true, true, true + } + }, + [377105] = { + [190510] = { + false, true, true + } + }, + [373932] = { + [190187] = { + true, false, false + } + }, + -- AA + [387910] = { + [196200] = { + false, true, true + } + }, + [387975] = { + [196202] = { + true, true, true + } + }, + [388863] = { + [196045] = { + true, true, true + } + }, + [388392] = { + [196044] = { + true, true, true + } + }, + [396812] = { + [196576] = { + true, true, true + } + }, + [377389] = { + [192333] = { + true, false, false + } + }, + [397888] = { + [200126] = { + true, true, true + } + }, + [397801] = { + [56448] = { + true, false, false + } + }, + [395859] = { + [59555] = { + true, true, true + } + }, + [395872] = { + [59546] = { + true, false, false + } + }, + [396018] = { + [59552] = { + true, false, false + } + }, + [396073] = { + [59544] = { + true, true, false + } + }, + [397899] = { + [200131] = { + false, true, true + } + }, + [397914] = { + [200137] = { + true, true, true + } + }, + -- sbg + [152818] = { + [75713] = { + true, true, false + } + }, + [398154] = { + [75451] = { + false, true, true + } + }, + [156776] = { + [76446] = { + true, true, true + } + }, + [156772] = { + [77700] = { + true, false, false + } + }, + [153524] = { + [75459] = { + true, true, true + } + }, + [156718] = { + [76104] = { + true, false, false + } + }, + [225100] = { + [104270] = { + true, false, false + } + }, + [210261] = { + [104251] = { + true, true, true + } + }, + [209027] = { + [104246] = { + false, true, true + } + }, + [212031] = { + [105705] = { + false, true, false + } + }, + [212784] = { + [105715] = { + false, true, false + } + }, + [198585] = { + [95842] = { + true, true, true + } + }, + [198959] = { + [96664] = { + true, true, true + } + }, + [215433] = { + [95834] = { + true, true, true + } + }, + [199210] = { + [96640] = { + false, true, true + } + }, + [199090] = { + [96611] = { + false, true, true + } + }, + [185425] = { + [96677] = { + false, true, false + } + }, } Bastion.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras) @@ -175,9 +461,21 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent) if castingSpell then local spellID = castingSpell:GetID() - local isKick = unpack(self.kickList[spellID] or { false, false }) + local kickEntry = self.kickList[spellID] + if not kickEntry then + return false + end + + local npcTraits = kickEntry[unit:GetID()] + + if not npcTraits then + return false + end + + local isKick, isStun, isDisorient = unpack(npcTraits) if isKick and unit:IsInterruptibleAt(percent) then + print("I want to kick", unit:GetName(), unit:GetID() .. "'s " .. spellID) return true end end @@ -193,8 +491,21 @@ function MythicPlusUtils:CastingCriticalStun(unit, percent) if castingSpell then local spellID = castingSpell:GetID() - local _, isStun = unpack(self.kickList[spellID] or { false, false }) - if isStun and unit:IsInterruptibleAt(percent, true) then + local kickEntry = self.kickList[spellID] + if not kickEntry then + return false + end + + local npcTraits = kickEntry[unit:GetID()] + + if not npcTraits then + return false + end + + local isKick, isStun, isDisorient = unpack(npcTraits) + + if (isStun or isDisorient) and not isKick and unit:IsInterruptibleAt(percent, true) then + print("I want to stun", unit:GetName(), unit:GetID() .. "'s " .. spellID) return true end end diff --git a/src/Sequencer/Sequencer.lua b/src/Sequencer/Sequencer.lua new file mode 100644 index 0000000..a44004d --- /dev/null +++ b/src/Sequencer/Sequencer.lua @@ -0,0 +1,87 @@ +-- Create a sequencer class that takes a table of actions and executes them in order + +---@class Sequencer +local Sequencer = {} +Sequencer.__index = Sequencer + +-- Constructor +---@param actions table +---@return Sequencer +function Sequencer:New(actions, resetCondition) + local self = setmetatable({}, Sequencer) + + self.actions = actions + self.index = 1 + + self.resetCondition = resetCondition + + return self +end + +-- Should we reset the sequencer +---@return boolean +function Sequencer:ShouldReset() + if self.resetCondition then + return self.resetCondition() + end + + return false +end + +-- Should we abort the sequencer +---@return boolean +function Sequencer:ShouldAbort() + if self.abortCondition then + return self.abortCondition() + end + + return false +end + +-- Execute the next action in the sequence if it doesn't return true we need to try it again +---@return boolean +function Sequencer:Next() + if self:Finished() then + print("Its finished?") + return false + end + + local action = self.actions[self.index] + print("Attempting action: " .. self.index .. "") + if action(self) then + self.index = self.index + 1 + return true + end + + return false +end + +-- Reset the sequencer +---@return nil +function Sequencer:Reset() + self.index = 1 +end + +function Sequencer:Execute() + if self:Next() then + return true + end + + return false +end + +function Sequencer:Finished() + return self.index > #self.actions +end + +function Sequencer:Abort() + self.index = #self.actions + 1 +end + +-- tostring +---@return string +function Sequencer:__tostring() + return "Bastion.__Sequencer" +end + +return Sequencer diff --git a/src/Spell/Spell.lua b/src/Spell/Spell.lua index aed4458..c33d7c6 100644 --- a/src/Spell/Spell.lua +++ b/src/Spell/Spell.lua @@ -7,9 +7,9 @@ local Spell = { PreCastFunc = false, OnCastFunc = false, PostCastFunc = false, - lastCastAttempt = 0, + lastCastAttempt = false, wasLooking = false, - lastCastAt = 0, + lastCastAt = false, conditions = {}, target = false, } @@ -119,6 +119,13 @@ function Spell:OnCooldown() return self:GetCooldownRemaining() > 0 end +-- Clear castable function +---@return Spell +function Spell:ClearCastableFunction() + self.CastableIfFunc = false + return self +end + -- Cast the spell ---@param unit Unit ---@param condition string @@ -163,6 +170,42 @@ function Spell:Cast(unit, condition) return true end +-- ForceCast the spell +---@param unit Unit +---@param condition string +---@return boolean +function Spell:ForceCast(unit) + -- Call pre cast function + -- if self:GetPreCastFunction() then + -- self:GetPreCastFunction()(self) + -- end + + -- Check if the mouse was looking + self.wasLooking = IsMouselooking() + + -- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast + local u = unit:GetOMToken() + if type(u) == "string" and string.find(u, 'nameplate') then + u = Object(u) + end + + -- Cast the spell + CastSpellByName(self:GetName(), u) + SpellCancelQueuedSpell() + + Bastion:Debug("Casting", self) + + -- Set the last cast time + self.lastCastAttempt = GetTime() + + -- -- Call post cast function + -- if self:GetOnCastFunction() then + -- self:GetOnCastFunction()(self) + -- end + + return true +end + -- Get post cast func ---@return fun(self:Spell) function Spell:GetPostCastFunction() @@ -187,7 +230,7 @@ end ---@return boolean function Spell:IsUsable() local usable, noMana = IsUsableSpell(self:GetID()) - return usable or usableExcludes[self:GetID()] + return usable or usableExcludes[self:GetID()] and not noMana end -- Check if the spell is castable @@ -321,6 +364,10 @@ function Spell:GetCharges() return GetSpellCharges(self:GetID()) end +function Spell:GetMaxCharges() + return select(2, GetSpellCharges(self:GetID())) +end + -- Get the spells charges ---@return number function Spell:GetChargesFractional() diff --git a/src/Unit/Unit.lua b/src/Unit/Unit.lua index 5e1f549..81155cf 100644 --- a/src/Unit/Unit.lua +++ b/src/Unit/Unit.lua @@ -13,6 +13,9 @@ local Unit = { last_off_attack = 0, last_main_attack = 0, last_combat_time = 0, + ttd_ticker = 0, + ttd = 0, + id = false, } function Unit:__index(k) @@ -449,6 +452,7 @@ end -- Check if unit is interruptible ---@param percent number +---@param ignoreInterruptible boolean ---@return boolean function Unit:IsInterruptibleAt(percent, ignoreInterruptible) if not ignoreInterruptible and not self:IsInterruptible() then @@ -626,6 +630,13 @@ function Unit:IsBehind(unit) return math.abs(angle) > 90 end +-- IsInfront +---@param unit Unit +---@return boolean +function Unit:IsInfront(unit) + return not self:IsBehind(unit) +end + ---@return number function Unit:GetMeleeBoost() if IsPlayerSpell(196924) then @@ -669,7 +680,9 @@ end -- Get object id ---@return number function Unit:GetID() - return ObjectID(self:GetOMToken()) + if self.id then return self.id end + self.id = ObjectID(self:GetOMToken()) + return self.id end -- In party @@ -714,7 +727,7 @@ function Unit:PredictHealth(time) local x = {} local y = {} - if #self.regression_history > 20 then + if #self.regression_history > 60 then table.remove(self.regression_history, 1) end @@ -737,7 +750,7 @@ function Unit:PredictTime(percent) local x = {} local y = {} - if #self.regression_history > 20 then + if #self.regression_history > 60 then table.remove(self.regression_history, 1) end @@ -753,21 +766,50 @@ function Unit:PredictTime(percent) return (percent - intercept) / slope end +-- Start time to die ticker +function Unit:StartTTDTicker() + if self.ttd_ticker then + return + end + + self.ttd_ticker = C_Timer.NewTicker(0.5, function() + local timeto = self:PredictTime(0) - GetTime() + self.ttd = timeto + end) +end + -- Time until death ---@return number function Unit:TimeToDie() if self:IsDead() then self.regression_history = {} + if self.ttd_ticker then + self.ttd_ticker:Cancel() + self.ttd_ticker = nil + end + return 0 + end + + if not self.ttd_ticker then + self:StartTTDTicker() + end + + -- If there's not enough data to make a prediction return 0 unless the unit has more than 5 million health + if #self.regression_history < 5 and self:GetMaxHealth() < 5000000 then return 0 end - local timeto = self:PredictTime(0) - GetTime() + -- if the unit has more than 5 million health but there's not enough data to make a prediction we can assume there's roughly 250000 damage per second and estimate the time to die + if #self.regression_history < 5 and self:GetMaxHealth() > 5000000 then + return self:GetMaxHealth() / + 250000 -- 250000 is an estimate of the average damage per second a well geared group will average + end - if timeto ~= timeto or timeto < 0 or timeto == math.huge then + if self.ttd ~= self.ttd or self.ttd < 0 or self.ttd == math.huge then return 0 end - return timeto + return self.ttd end -- Set combat time if affecting combat and return the difference between now and the last time @@ -820,7 +862,12 @@ function Unit:GetMaxGCD() haste = 50 end - return 1.5 / (1 + haste / 100) + -- if the unit uses focus their gcd is 1.0 seconds not 1.5 + local base = 1.5 + if self:GetPowerType() == 3 then + base = 1.0 + end + return base / (1 + haste / 100) end -- IsStealthed diff --git a/src/_bastion.lua b/src/_bastion.lua index f701f55..7e52165 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -18,6 +18,8 @@ Bastion.List = Bastion.require("List") Bastion.NotificationsList, Bastion.Notification = Bastion.require("NotificationsList") ---@type Vector3 Bastion.Vector3 = Bastion.require("Vector3") +---@type Sequencer +Bastion.Sequencer = Bastion.require("Sequencer") ---@type Command Bastion.Command = Bastion.require("Command") ---@type Cache @@ -85,8 +87,24 @@ Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) end end) +local pguid = UnitGUID("player") +local missed = {} + 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() + local args = { CombatLogGetCurrentEventInfo() } + + local subEvent = args[2] + local sourceGUID = args[4] + local destGUID = args[8] + local spellID = args[12] + + -- if sourceGUID == pguid then + -- local args = { CombatLogGetCurrentEventInfo() } + + -- for i = 1, #args do + -- Log(tostring(args[i])) + -- end + -- end local u = Bastion.UnitManager[sourceGUID] local u2 = Bastion.UnitManager[destGUID] @@ -99,23 +117,37 @@ Bastion.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() if u2 then u2:SetLastCombatTime(t) + + if subEvent == "SPELL_MISSED" and sourceGUID == pguid and spellID == 408 then + local missType = args[15] + + if missType == "IMMUNE" then + local castingSpell = u:GetCastingOrChannelingSpell() + + if castingSpell then + if not missed[castingSpell:GetID()] then + missed[castingSpell:GetID()] = true + end + end + end + end end end) Bastion.Ticker = C_Timer.NewTicker(0.1, function() - if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then - Bastion.CombatTimer:Start() - elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then - Bastion.CombatTimer:Reset() - end + if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then + Bastion.CombatTimer:Start() + elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then + Bastion.CombatTimer:Reset() + end - if Bastion.Enabled then - Bastion.ObjectManager:Refresh() - for i = 1, #Bastion.modules do - Bastion.modules[i]:Tick() + if Bastion.Enabled then + Bastion.ObjectManager:Refresh() + for i = 1, #Bastion.modules do + Bastion.modules[i]:Tick() + end end - end -end) + end) function Bastion:Register(module) table.insert(Bastion.modules, module) @@ -228,11 +260,17 @@ Command:Register('mplus', 'Toggle m+ module on/off', function(args) Bastion:Print("casts") end) +Command:Register('missed', 'Dump the list of immune kidney shot spells', function() + for k, v in pairs(missed) do + Bastion:Print(k) + end +end) + local files = ListFiles("scripts/bastion/scripts") for i = 1, #files do local file = files[i] - if file:sub(-4) == ".lua" or file:sub(-5) == '.luac' then + if file:sub( -4) == ".lua" or file:sub( -5) == '.luac' then Tinkr:require("scripts/bastion/scripts/" .. file:sub(1, -5), Bastion) end end From 29fc81e1f358643f1d92184b9833cde43b49cb3e Mon Sep 17 00:00:00 2001 From: 4n0n <4n0n@tinkr.site> Date: Thu, 16 Feb 2023 18:43:45 -0600 Subject: [PATCH 3/3] Optimizations in sub, remove prints --- scripts/subtlety.lua | 34 +++++++++++++++---------- src/MythicPlusUtils/MythicPlusUtils.lua | 2 -- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/scripts/subtlety.lua b/scripts/subtlety.lua index e5ab155..d1432f6 100644 --- a/scripts/subtlety.lua +++ b/scripts/subtlety.lua @@ -263,6 +263,8 @@ local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function() return target end) + + local DefaultAPL = Bastion.APL:New('default') local ItemsAPL = Bastion.APL:New('items') local DefensivesAPL = Bastion.APL:New('defensives') @@ -404,13 +406,13 @@ TrinketsAPL:AddItem( 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) --- ) +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( @@ -942,7 +944,6 @@ local function ShouldPoolForCD() if power > 80 then return false end if SD < 3 and power < 80 then - print("Whys this true") return true end @@ -954,7 +955,8 @@ 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 and self:IsInRange(Target) + (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) ) @@ -1118,9 +1120,12 @@ CDsAPL:AddSpell( Vanish:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and - Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and Player:GetEnemies(10) <= 1 and - SecretTechnique:GetTimeSinceLastCast() <= 1 and Vanish:GetCharges() > 1 - end):SetTarget(Player) + 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() @@ -1162,7 +1167,7 @@ FinishersAPL:AddSpell( -- 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 + 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() @@ -1239,7 +1244,8 @@ DefaultAPL:AddAPL( FinishersAPL, function() return Player:GetComboPoints() >= 6 or - (Player:GetComboPoints() >= 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) + (Player:GetComboPoints() >= 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) or + (Player:GetEnemies(10) > 3 and Player:GetComboPoints() >= 4) end ) diff --git a/src/MythicPlusUtils/MythicPlusUtils.lua b/src/MythicPlusUtils/MythicPlusUtils.lua index 27d4b52..e517e00 100644 --- a/src/MythicPlusUtils/MythicPlusUtils.lua +++ b/src/MythicPlusUtils/MythicPlusUtils.lua @@ -475,7 +475,6 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent) local isKick, isStun, isDisorient = unpack(npcTraits) if isKick and unit:IsInterruptibleAt(percent) then - print("I want to kick", unit:GetName(), unit:GetID() .. "'s " .. spellID) return true end end @@ -505,7 +504,6 @@ function MythicPlusUtils:CastingCriticalStun(unit, percent) local isKick, isStun, isDisorient = unpack(npcTraits) if (isStun or isDisorient) and not isKick and unit:IsInterruptibleAt(percent, true) then - print("I want to stun", unit:GetName(), unit:GetID() .. "'s " .. spellID) return true end end