--- ============================ HEADER ============================ --- ======= LOCALIZE ======= -- Addon local addonName, addonTable = ... -- HeroDBC local DBC = HeroDBC.DBC -- HeroLib local HL = HeroLib local Cache = HeroCache local Unit = HL.Unit local Player = Unit.Player local Target = Unit.Target local Pet = Unit.Pet local Spell = HL.Spell local Item = HL.Item -- HeroRotation local HR = HeroRotation local Cast = HR.Cast local AoEON = HR.AoEON local CDsON = HR.CDsON local Warlock = HR.Commons.Warlock -- Num/Bool Helper Functions local num = HR.Commons.Everyone.num local bool = HR.Commons.Everyone.bool --- ============================ CONTENT =========================== --- ======= APL LOCALS ======= -- luacheck: max_line_length 9999 -- Define S/I for spell and item arrays local S = Spell.Warlock.Demonology local I = Item.Warlock.Demonology -- Create table to exclude above trinkets from On Use function local OnUseExcludes = { I.BeacontotheBeyond:ID(), I.EruptingSpearFragment:ID(), I.IrideusFragment:ID(), I.RotcrustedVoodooDoll:ID(), I.SpoilsofNeltharus:ID(), I.TimebreachingTalon:ID(), I.VoidmendersShadowgem:ID(), } -- Trinket Item Objects local equip = Player:GetEquipment() -- Rotation Var local BossFightRemains = 11111 local FightRemains = 11111 local VarNextTyrant = 0 local VarNPCondition = false local VarShadowTimings = 0 local VarTyrantCD = 120 local VarTyrantPrepStart = 0 local CombatTime = 0 -- Enemy Variables local Enemies40y local Enemies8ySplash, EnemiesCount8ySplash HL:RegisterForEvent(function() BossFightRemains = 11111 FightRemains = 11111 end, "PLAYER_REGEN_ENABLED") -- GUI Settings local Everyone = HR.Commons.Everyone local Settings = { General = HR.GUISettings.General, Commons = HR.GUISettings.APL.Warlock.Commons, Demonology = HR.GUISettings.APL.Warlock.Demonology } -- Stuns local StunInterrupts = { {S.AxeToss, "Cast Axe Toss (Interrupt)", function () return true; end}, } HL:RegisterForEvent(function() equip = Player:GetEquipment() end, "PLAYER_EQUIPMENT_CHANGED") HL:RegisterForEvent(function() S.HandofGuldan:RegisterInFlight() end, "LEARNED_SPELL_IN_TAB") S.HandofGuldan:RegisterInFlight() -- Function to check for imp count local function WildImpsCount() return HL.GuardiansTable.ImpCount or 0 end -- Function to check two_cast_imps or last_cast_imps local function CheckImpCasts(count) local ImpCount = 0 for _, Pet in pairs(HL.GuardiansTable.Pets) do if Pet.ImpCasts <= count then ImpCount = ImpCount + 1 end end return ImpCount end -- Function to check for remaining Grimoire Felguard duration local function GrimoireFelguardTime() return HL.GuardiansTable.FelGuardDuration or 0 end -- Function to check for Demonic Tyrant duration local function DemonicTyrantTime() return HL.GuardiansTable.DemonicTyrantDuration or 0 end -- Function to check for Dreadstalker duration local function DreadstalkerTime() return HL.GuardiansTable.DreadstalkerDuration or 0 end -- Function to check for Pit Lord duration local function PitLordTime() return HL.GuardiansTable.PitLordDuration or 0 end local function EvaluateDoom(TargetUnit) -- target_if=refreshable return (TargetUnit:DebuffRefreshable(S.Doom)) end local function Precombat() -- flask -- food -- augmentation -- summon_pet -- Moved to APL() -- snapshot_stats -- variable,name=tyrant_prep_start,op=set,value=12 VarTyrantPrepStart = 12 -- variable,name=next_tyrant,op=set,value=14+talent.grimoire_felguard+talent.summon_vilefiend VarNextTyrant = 14 + num(S.GrimoireFelguard:IsAvailable()) + num(S.SummonVilefiend:IsAvailable()) -- variable,name=shadow_timings,default=0,op=reset VarShadowTimings = 0 -- variable,name=shadow_timings,op=set,value=0,if=cooldown.invoke_power_infusion_0.duration!=120 if Settings.Demonology.PISource == "Shadow" then VarShadowTimings = 1 end -- power_siphon if S.PowerSiphon:IsReady() then if Cast(S.PowerSiphon, Settings.Demonology.GCDasOffGCD.PowerSiphon) then return "power_siphon precombat 2"; end end -- demonbolt,if=!buff.power_siphon.up if S.Demonbolt:IsReady() and Player:BuffDown(S.DemonicCoreBuff) then if Cast(S.Demonbolt, nil, nil, not Target:IsSpellInRange(S.Demonbolt)) then return "demonbolt precombat 4"; end end -- shadow_bolt if S.ShadowBolt:IsReady() then if Cast(S.ShadowBolt, nil, nil, not Target:IsSpellInRange(S.ShadowBolt)) then return "shadow_bolt precombat 6"; end end end local function Variables() -- variable,name=tyrant_cd,op=setif,value=cooldown.invoke_power_infusion_0.remains,value_else=cooldown.summon_demonic_tyrant.remains_expected,condition=((((fight_remains+time)%%120<=85&(fight_remains+time)%%120>=25)|time>=210)&variable.shadow_timings)&cooldown.invoke_power_infusion_0.duration>0&!talent.grand_warlocks_design VarTyrantCD = S.SummonDemonicTyrant:CooldownRemains() if bool(VarShadowTimings) then local VarPICD = 120 - (GetTime() - Warlock.LastPI) -- Note: Moved VarPICD check to the front to avoid unnecessary calculations. if VarPICD > 0 and ((((FightRemains + CombatTime) % 120 <= 85 and (FightRemains + CombatTime) % 120 >= 25) or CombatTime >= 210) and VarShadowTimings) and not S.GrandWarlocksDesign:IsAvailable() then VarTyrantCD = VarPICD end end -- variable,name=np_condition,op=set,value=cooldown.nether_portal.up|buff.nether_portal.up|pet.pit_lord.active|!talent.nether_portal|cooldown.nether_portal.remains>30 VarNPCondition = (S.NetherPortal:CooldownUp() or Player:BuffUp(S.NetherPortalBuff) or PitLordTime() > 0 or (not S.NetherPortal:IsAvailable()) or S.NetherPortal:CooldownRemains() > 30) end local function Tyrant() -- variable,name=next_tyrant,op=set,value=time+13+cooldown.grimoire_felguard.ready+cooldown.summon_vilefiend.ready,if=variable.next_tyrant<=time&!equipped.neltharions_call_to_dominance if (VarNextTyrant <= CombatTime and not I.NeltharionsCallToDominance:IsEquipped()) then VarNextTyrant = CombatTime + 13 + num(S.GrimoireFelguard:CooldownUp()) + num(S.SummonVilefiend:CooldownUp()) end -- invoke_external_buff,name=power_infusion,if=(buff.nether_portal.up&buff.nether_portal.remains<8&talent.nether_portal)|(buff.dreadstalkers.up&variable.next_tyrant-time<=6&(!talent.nether_portal|variable.shadow_timings)) -- Note: Not handling external buffs -- shadow_bolt,if=time<2&soul_shard<5 if S.ShadowBolt:IsCastable() and (CombatTime < 2 and Player:SoulShardsP() < 5) then if Cast(S.ShadowBolt, nil, nil, not Target:IsSpellInRange(S.ShadowBolt)) then return "shadow_bolt tyrant 2"; end end -- nether_portal if S.NetherPortal:IsReady() then if Cast(S.NetherPortal, Settings.Demonology.GCDasOffGCD.NetherPortal) then return "nether_portal tyrant 4"; end end -- variable,name=next_tyrant,op=set,value=time+13+cooldown.grimoire_felguard.ready+cooldown.summon_vilefiend.ready,if=variable.next_tyrant<=time&equipped.neltharions_call_to_dominance if VarNextTyrant <= CombatTime and I.NeltharionsCallToDominance:IsEquipped() then VarNextTyrant = CombatTime + 13 + num(S.GrimoireFelguard:CooldownUp()) + num(S.SummonVilefiend:CooldownUp()) end -- grimoire_felguard if S.GrimoireFelguard:IsReady() then if Cast(S.GrimoireFelguard, Settings.Demonology.GCDasOffGCD.GrimoireFelguard, nil, not Target:IsSpellInRange(S.GrimoireFelguard)) then return "grimoire_felguard tyrant 6"; end end -- summon_vilefiend if S.SummonVilefiend:IsReady() then if Cast(S.SummonVilefiend) then return "summon_vilefiend tyrant 8"; end end -- call_dreadstalkers if S.CallDreadstalkers:IsReady() then if Cast(S.CallDreadstalkers, nil, nil, not Target:IsSpellInRange(S.CallDreadstalkers)) then return "call_dreadstalkers tyrant 10"; end end -- soulburn,if=buff.nether_portal.up&soul_shard>=2,line_cd=40 if S.Soulburn:IsReady() and S.Soulburn:TimeSinceLastCast() >= 40 and (Player:BuffUp(S.NetherPortalBuff) and Player:SoulShardsP() > 2) then if Cast(S.Soulburn) then return "soulburn tyrant 12"; end end -- hand_of_guldan,if=variable.next_tyrant-time>2&(buff.nether_portal.up|soul_shard>2&variable.next_tyrant-time<12|soul_shard=5)&!cooldown.call_dreadstalkers.up if S.HandofGuldan:IsReady() and (VarNextTyrant - CombatTime > 2 and (Player:BuffUp(S.NetherPortalBuff) or Player:SoulShardsP() > 2 and VarNextTyrant - CombatTime < 12 or Player:SoulShardsP() == 5) and S.CallDreadstalkers:CooldownDown()) then if Cast(S.HandofGuldan, nil, nil, not Target:IsSpellInRange(S.HandofGuldan)) then return "hand_of_guldan tyrant 14"; end end -- hand_of_guldan,if=talent.soulbound_tyrant&variable.next_tyrant-time<4&variable.next_tyrant-time>action.summon_demonic_tyrant.cast_time if S.HandofGuldan:IsReady() and (S.SoulboundTyrant:IsAvailable() and VarNextTyrant - CombatTime < 4 and VarNextTyrant - CombatTime > S.SummonDemonicTyrant:CastTime()) then if Cast(S.HandofGuldan, nil, nil, not Target:IsSpellInRange(S.HandofGuldan)) then return "hand_of_guldan tyrant 16"; end end -- summon_demonic_tyrant,if=variable.next_tyrant-time 0)) then if Cast(S.SummonDemonicTyrant, Settings.Demonology.GCDasOffGCD.SummonDemonicTyrant) then return "summon_demonic_tyrant tyrant 18"; end end -- demonbolt,if=buff.demonic_core.up -- Note: Sometimes suggested during Tyrant cast, which shouldn't happen as it would over-cap shards. if S.Demonbolt:IsReady() and (not Player:IsCasting(S.SummonDemonicTyrant)) and (Player:BuffUp(S.DemonicCoreBuff)) then if Cast(S.Demonbolt, nil, nil, not Target:IsSpellInRange(S.Demonbolt)) then return "demonbolt tyrant 20"; end end -- power_siphon,if=buff.wild_imps.stack>1&!buff.nether_portal.up if S.PowerSiphon:IsCastable() and (WildImpsCount() > 1 and not Player:BuffUp(S.NetherPortalBuff)) then if Cast(S.PowerSiphon, Settings.Demonology.GCDasOffGCD.PowerSiphon) then return "power_siphon tyrant 22"; end end -- soul_strike if S.SoulStrike:IsReady() then if Cast(S.SoulStrike, nil, nil, not Target:IsSpellInRange(S.SoulStrike)) then return "soul_strike tyrant 24"; end end -- shadow_bolt if S.ShadowBolt:IsCastable() then if Cast(S.ShadowBolt, nil, nil, not Target:IsSpellInRange(S.ShadowBolt)) then return "shadow_bolt tyrant 26"; end end end local function Items() if Settings.Commons.Enabled.Trinkets then -- use_item,name=irideus_fragment,if=buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal)|time_to_die<=21 -- use_item,name=timebreaching_talon,if=buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal)|time_to_die<=21 -- use_item,name=spoils_of_neltharus,if=buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal)|time_to_die<=21 if (Player:BuffUp(S.DemonicPowerBuff) or not S.SummonDemonicTyrant:IsAvailable() and (Player:BuffUp(S.NetherPortalBuff) or not S.NetherPortal:IsAvailable()) or FightRemains <= 21) then if I.IrideusFragment:IsEquippedAndReady() then if Cast(I.IrideusFragment, nil, Settings.Commons.DisplayStyle.Trinkets) then return "irideus_fragment items 2"; end end if I.TimebreachingTalon:IsEquippedAndReady() then if Cast(I.TimebreachingTalon, nil, Settings.Commons.DisplayStyle.Trinkets) then return "timebreaching_talon items 4"; end end if I.SpoilsofNeltharus:IsEquippedAndReady() then if Cast(I.SpoilsofNeltharus, nil, Settings.Commons.DisplayStyle.Trinkets) then return "spoils_of_neltharus items 6"; end end end -- use_item,name=voidmenders_shadowgem,if=!variable.shadow_timings|(variable.shadow_timings&(buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal))) if I.VoidmendersShadowgem:IsEquippedAndReady() and ((not VarShadowTimings) or (VarShadowTimings and (Player:BuffUp(S.DemonicPowerBuff) or (not S.SummonDemonicTyrant:IsAvailable()) and (Player:BuffUp(S.NetherPortalBuff) or not S.NetherPortal:IsAvailable())))) then if Cast(I.VoidmendersShadowgem, nil, Settings.Commons.DisplayStyle.Trinkets) then return "voidmenders_shadowgem items 8"; end end -- use_item,name=erupting_spear_fragment,if=buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal)|time_to_die<=11 if I.EruptingSpearFragment:IsEquippedAndReady() and (Player:BuffUp(S.DemonicPowerBuff) or (not S.SummonDemonicTyrant:IsAvailable()) and (Player:BuffUp(S.NetherPortalBuff) or not S.NetherPortal:IsAvailable()) or FightRemains <= 11) then if Cast(I.EruptingSpearFragment, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(40)) then return "erupting_spear_fragment items 10"; end end end -- use_items,if=(buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal))&(!equipped.irideus_fragment&!equipped.timebreaching_talon&!equipped.spoils_of_neltharus&!equipped.erupting_spear_fragment&!equipped.voidmenders_shadowgem) -- Note: Excluded trinkets are excluded via OnUseExcludes, so ignoring that portion of the condition. if (Player:BuffUp(S.DemonicPowerBuff) or (not S.SummonDemonicTyrant:IsAvailable()) and (Player:BuffUp(S.NetherPortalBuff) or not S.NetherPortal:IsAvailable())) then local ItemToUse, ItemSlot, ItemRange = Player:GetUseableItems(OnUseExcludes) if ItemToUse then local DisplayStyle = Settings.Commons.DisplayStyle.Trinkets if ItemSlot ~= 13 and ItemSlot ~= 14 then DisplayStyle = Settings.Commons.DisplayStyle.Items end if ((ItemSlot == 13 or ItemSlot == 14) and Settings.Commons.Enabled.Trinkets) or (ItemSlot ~= 13 and ItemSlot ~= 14 and Settings.Commons.Enabled.Items) then if Cast(ItemToUse, nil, DisplayStyle, not Target:IsInRange(ItemRange)) then return "Generic use_items for " .. ItemToUse:Name(); end end end end if Settings.Commons.Enabled.Trinkets then -- use_item,name=rotcrusted_voodoo_doll if I.RotcrustedVoodooDoll:IsEquippedAndReady() then if Cast(I.RotcrustedVoodooDoll, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(50)) then return "rotcrusted_voodoo_doll items 12"; end end -- use_item,name=beacon_to_the_beyond if I.BeacontotheBeyond:IsEquippedAndReady() then if Cast(I.BeacontotheBeyond, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(45)) then return "beacon_to_the_beyond items 14"; end end end end local function Ogcd() -- potion if Settings.Commons.Enabled.Potions then local PotionSelected = Everyone.PotionSelected() if PotionSelected and PotionSelected:IsReady() then if Cast(PotionSelected, nil, Settings.Commons.DisplayStyle.Potions) then return "potion ogcd 2"; end end end -- berserking if S.Berserking:IsCastable() then if Cast(S.Berserking, Settings.Commons.OffGCDasOffGCD.Racials) then return "berserking ogcd 4"; end end -- blood_fury if S.BloodFury:IsCastable() then if Cast(S.BloodFury, Settings.Commons.OffGCDasOffGCD.Racials) then return "blood_fury ogcd 6"; end end -- fireblood if S.Fireblood:IsCastable() then if Cast(S.Fireblood, Settings.Commons.OffGCDasOffGCD.Racials) then return "fireblood ogcd 8"; end end end --- ======= ACTION LISTS ======= local function APL() -- Update Enemy Counts if AoEON() then Enemies8ySplash = Target:GetEnemiesInSplashRange(8) EnemiesCount8ySplash = Target:GetEnemiesInSplashRangeCount(8) Enemies40y = Player:GetEnemiesInRange(40) else Enemies8ySplash = {} EnemiesCount8ySplash = 1 Enemies40y = {} end -- Update Demonology-specific Tables Warlock.UpdatePetTable() Warlock.UpdateSoulShards() if Everyone.TargetIsValid() or Player:AffectingCombat() then -- Calculate fight_remains BossFightRemains = HL.BossFightRemains(nil, true) FightRemains = BossFightRemains if FightRemains == 11111 then FightRemains = HL.FightRemains(Enemies8ySplash, false) end -- Update CombatTime, which is used in many spell suggestions CombatTime = HL.CombatTime() end -- summon_pet if S.SummonPet:IsCastable() and not (Player:IsMounted() or Player:IsInVehicle()) then if Cast(S.SummonPet, Settings.Demonology.GCDasOffGCD.SummonPet) then return "summon_pet ooc"; end end if Everyone.TargetIsValid() then -- call precombat if not Player:AffectingCombat() and not Player:IsCasting(S.Demonbolt) then local ShouldReturn = Precombat(); if ShouldReturn then return ShouldReturn; end end -- Interrupts if S.SpellLock:IsAvailable() then local ShouldReturn = Everyone.Interrupt(40, S.SpellLock, Settings.Commons.OffGCDasOffGCD.SpellLock, StunInterrupts); if ShouldReturn then return ShouldReturn; end end -- Manually added: unending_resolve if S.UnendingResolve:IsReady() and (Player:HealthPercentage() < Settings.Demonology.UnendingResolveHP) then if Cast(S.UnendingResolve, Settings.Demonology.OffGCDasOffGCD.UnendingResolve) then return "unending_resolve defensive"; end end -- call_action_list,name=variables Variables() -- call_action_list,name=tyrant,if=talent.summon_demonic_tyrant&(time-variable.next_tyrant)<=(variable.tyrant_prep_start+2)&cooldown.summon_demonic_tyrant.up&variable.np_condition -- Note: Reversed VarNextTyrant and CombatTime in the calculation, as otherwise the condition was never true after the initial Tyrant. if CDsON() and S.SummonDemonicTyrant:IsAvailable() and (VarNextTyrant - CombatTime) <= (VarTyrantPrepStart + 2) and S.SummonDemonicTyrant:CooldownUp() and VarNPCondition then local ShouldReturn = Tyrant(); if ShouldReturn then return ShouldReturn; end end -- call_action_list,name=tyrant,if=talent.summon_demonic_tyrant&(variable.tyrant_cd<=variable.tyrant_prep_start|cooldown.summon_demonic_tyrant.up&(buff.power_infusion.up|buff.nether_portal.up))&variable.np_condition if CDsON() and S.SummonDemonicTyrant:IsAvailable() and (S.SummonDemonicTyrant:CooldownUp() and (Player:BuffUp(S.PowerInfusionBuff) or Player:BuffUp(S.NetherPortalBuff))) and VarNPCondition then local ShouldReturn = Tyrant(); if ShouldReturn then return ShouldReturn; end end -- invoke_external_buff,name=power_infusion,if=!talent.nether_portal&!talent.summon_demonic_tyrant|time_to_die<25|(buff.tyrant.up&variable.shadow_timings) -- Note: Not handling external buffs -- implosion,if=time_to_die<2*gcd if S.Implosion:IsReady() and (FightRemains < 2 * Player:GCD()) then if Cast(S.Implosion, Settings.Demonology.GCDasOffGCD.Implosion, nil, not Target:IsSpellInRange(S.Implosion)) then return "implosion main 2"; end end -- nether_portal,if=!talent.summon_demonic_tyrant&soul_shard>2|time_to_die<30 if CDsON() and S.NetherPortal:IsReady() and ((not S.SummonDemonicTyrant:IsAvailable()) and (Player:SoulShardsP() > 2) or FightRemains < 30) then if Cast(S.NetherPortal, Settings.Demonology.GCDasOffGCD.NetherPortal) then return "nether_portal main 4"; end end -- call_action_list,name=items if Settings.Commons.Enabled.Trinkets or Settings.Commons.Enabled.Items then local ShouldReturn = Items(); if ShouldReturn then return ShouldReturn; end end -- call_action_list,name=ogcd,if=buff.demonic_power.up|!talent.summon_demonic_tyrant&(buff.nether_portal.up|!talent.nether_portal) if CDsON() and (Player:BuffUp(S.DemonicPowerBuff) or (not S.SummonDemonicTyrant:IsAvailable()) and (Player:BuffUp(S.NetherPortalBuff) or not S.NetherPortal:IsAvailable())) then local ShouldReturn = Ogcd(); if ShouldReturn then return ShouldReturn; end end -- hand_of_guldan,if=buff.nether_portal.remains>cast_time if S.HandofGuldan:IsReady() and (Player:BuffRemains(S.NetherPortalBuff) > S.HandofGuldan:CastTime()) then if Cast(S.HandofGuldan, nil, nil, not Target:IsSpellInRange(S.HandofGuldan)) then return "hand_of_guldan main 6"; end end -- call_dreadstalkers,if=variable.tyrant_cd>cooldown+8*variable.shadow_timings if S.CallDreadstalkers:IsReady() and (VarTyrantCD > 20 + 8 * VarShadowTimings) then if Cast(S.CallDreadstalkers, nil, nil, not Target:IsSpellInRange(S.CallDreadstalkers)) then return "call_dreadstalkers main 8"; end end -- call_dreadstalkers,if=!talent.summon_demonic_tyrant|time_to_die<14 if S.CallDreadstalkers:IsReady() and ((not S.SummonDemonicTyrant:IsAvailable()) or FightRemains < 14) then if Cast(S.CallDreadstalkers, nil, nil, not Target:IsSpellInRange(S.CallDreadstalkers)) then return "call_dreadstalkers main 10"; end end -- grimoire_felguard,if=!talent.summon_demonic_tyrant|time_to_diecooldown+variable.tyrant_prep_start|time_to_die 45 + VarTyrantPrepStart or FightRemains < S.SummonDemonicTyrant:CooldownRemains()) then if Cast(S.SummonVilefiend) then return "summon_vilefiend main 14"; end end -- guillotine,if=cooldown.demonic_strength.remains -- Added check to make sure that we're not suggesting this during pet's Felstorm or Demonic Strength if S.Guillotine:IsReady() and S.Felstorm:CooldownRemains() < 30 - S.Felstorm:TickTime() * 5 and S.DemonicStrength:TimeSinceLastCast() > S.Felstorm:TickTime() * 5 and (S.DemonicStrength:CooldownDown() or not S.DemonicStrength:IsAvailable()) then if Cast(S.Guillotine, nil, nil, not Target:IsInRange(40)) then return "guillotine main 16"; end end -- demonic_strength -- Added check to make sure that we're not suggesting this during pet's Felstorm or Guillotine if CDsON() and S.DemonicStrength:IsReady() and S.Felstorm:CooldownRemains() < S.Felstorm:TickTime() * 5 and S.Guillotine:TimeSinceLastCast() >= 8 then if Cast(S.DemonicStrength, Settings.Demonology.GCDasOffGCD.DemonicStrength) then return "demonic_strength main 18"; end end -- bilescourge_bombers,if=!pet.demonic_tyrant.active if S.BilescourgeBombers:IsReady() and (DemonicTyrantTime() == 0) then if Cast(S.BilescourgeBombers, nil, nil, not Target:IsInRange(40)) then return "bilescourge_bombers main 20"; end end -- shadow_bolt,if=soul_shard<5&talent.fel_covenant&buff.fel_covenant.remains<5 if S.ShadowBolt:IsCastable() and (Player:SoulShardsP() < 5 and S.FelCovenant:IsAvailable() and Player:BuffRemains(S.FelCovenantBuff) < 5) then if Cast(S.ShadowBolt, nil, nil, not Target:IsSpellInRange(S.ShadowBolt)) then return "shadow_bolt main 22"; end end -- implosion,if=two_cast_imps>0&buff.tyrant.down&active_enemies>1+(talent.sacrificed_souls.enabled) if S.Implosion:IsReady() and (CheckImpCasts(2) > 0 and DemonicTyrantTime() == 0 and EnemiesCount8ySplash > 1 + num(S.SacrificedSouls:IsAvailable())) then if Cast(S.Implosion, Settings.Demonology.GCDasOffGCD.Implosion, nil, not Target:IsInRange(40)) then return "implosion main 24"; end end -- implosion,if=buff.wild_imps.stack>9&buff.tyrant.up&active_enemies>2+(1*talent.sacrificed_souls.enabled)&cooldown.call_dreadstalkers.remains>17&talent.the_expendables if S.Implosion:IsReady() and (WildImpsCount() > 9 and DemonicTyrantTime() > 0 and EnemiesCount8ySplash > 2 + num(S.SacrificedSouls:IsAvailable()) and S.CallDreadstalkers:CooldownRemains() > 17 and S.TheExpendables:IsAvailable()) then if Cast(S.Implosion, Settings.Demonology.GCDasOffGCD.Implosion, nil, not Target:IsInRange(40)) then return "implosion main 26"; end end -- implosion,if=active_enemies=1&last_cast_imps>0&buff.tyrant.down&talent.imp_gang_boss.enabled&!talent.sacrificed_souls if S.Implosion:IsReady() and (EnemiesCount8ySplash == 1 and CheckImpCasts(1) > 0 and DemonicTyrantTime() == 0 and S.ImpGangBoss:IsAvailable() and not S.SacrificedSouls:IsAvailable()) then if Cast(S.Implosion, Settings.Demonology.GCDasOffGCD.Implosion, nil, not Target:IsInRange(40)) then return "implosion main 28"; end end -- soul_strike,if=soul_shard<5&active_enemies>1 if S.SoulStrike:IsReady() and (Player:SoulShardsP() < 5 and EnemiesCount8ySplash > 1) then if Cast(S.SoulStrike, nil, nil, not Target:IsSpellInRange(S.SoulStrike)) then return "soul_strike main 30"; end end -- summon_soulkeeper,if=buff.tormented_soul.stack=10&active_enemies>1 if S.SummonSoulkeeper:IsReady() and (S.SummonSoulkeeper:Count() == 10 and EnemiesCount8ySplash > 1) then if Cast(S.SummonSoulkeeper) then return "soul_strike main 32"; end end -- demonbolt,if=buff.demonic_core.react&soul_shard<4&variable.tyrant_cd>5 if S.Demonbolt:IsReady() and (Player:BuffUp(S.DemonicCoreBuff) and Player:SoulShardsP() < 4 and VarTyrantCD > 5) then if Cast(S.Demonbolt, nil, nil, not Target:IsSpellInRange(S.Demonbolt)) then return "demonbolt main 34"; end end -- power_siphon,if=buff.demonic_core.stack<2&(buff.dreadstalkers.remains>gcd*3|buff.dreadstalkers.down) if S.PowerSiphon:IsReady() and (Player:BuffStack(S.DemonicCoreBuff) < 2 and (DreadstalkerTime() > Player:GCD() * 3 or DreadstalkerTime() == 0)) then if Cast(S.PowerSiphon, Settings.Demonology.GCDasOffGCD.PowerSiphon) then return "power_siphon main 36"; end end -- hand_of_guldan,if=soul_shard>2&(!talent.summon_demonic_tyrant|variable.tyrant_cd>variable.tyrant_prep_start+2)&(buff.demonic_calling.up|soul_shard>4|cooldown.call_dreadstalkers.remains>gcd) if S.HandofGuldan:IsReady() and (Player:SoulShardsP() > 2 and ((not S.SummonDemonicTyrant:IsAvailable()) or VarTyrantCD > VarTyrantPrepStart + 2 or not CDsON()) and (Player:BuffUp(S.DemonicCallingBuff) or Player:SoulShardsP() > 4 or S.CallDreadstalkers:CooldownRemains() > Player:GCD())) then if Cast(S.HandofGuldan, nil, nil, not Target:IsSpellInRange(S.HandofGuldan)) then return "hand_of_guldan main 38"; end end -- doom,target_if=refreshable if S.Doom:IsReady() then if Everyone.CastCycle(S.Doom, Enemies40y, EvaluateDoom, not Target:IsSpellInRange(S.Doom)) then return "doom main 40"; end end -- soul_strike,if=soul_shard<5 if S.SoulStrike:IsReady() and (Player:SoulShardsP() < 5) then if Cast(S.SoulStrike, nil, nil, not Target:IsSpellInRange(S.SoulStrike)) then return "soul_strike main 42"; end end -- shadow_bolt if S.ShadowBolt:IsCastable() then if Cast(S.ShadowBolt, nil, nil, not Target:IsSpellInRange(S.ShadowBolt)) then return "shadow_bolt main 44"; end end end end local function Init() HR.Print("Demonology Warlock rotation is currently a work in progress, but has been updated for patch 10.1.0.") end HR.SetAPL(266, APL, Init)