Bastion aims to serve as a highly performant, simplisitic, and expandable World of Warcraft data visualization framework.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Bastion/references/subtlety.lua

1380 lines
47 KiB

2 years ago
local Tinkr, Bastion = ...
local SubModulue = Bastion.Module:New('sub')
local Evaluator = Tinkr.Util.Evaluator
local Player = Bastion.UnitManager:Get('player')
local None = Bastion.UnitManager:Get('none')
local Target = Bastion.UnitManager:Get('target')
Player:WatchForSwings()
2 years ago
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)
2 years ago
local RefreshingHealingPotion = Bastion.ItemBook:GetItem(191380)
2 years ago
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)
2 years ago
2 years ago
local DeadgeHealth = 120000 * 12
2 years ago
2 years ago
local function IsDeadge(unit) return false end
2 years ago
2 years ago
local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit)
local purge = nil
2 years ago
2 years ago
Bastion.UnitManager:EnumEnemies(function(unit)
if unit:IsDead() then
return false
end
2 years ago
2 years ago
if not Player:CanSee(unit) then
return false
end
2 years ago
2 years ago
if Player:GetDistance(unit) > 40 then
return false
end
2 years ago
2 years ago
if unit:GetAuras():HasAnyStealableAura() and Shiv:IsInRange(unit) then
purge = unit
return true
end
end)
2 years ago
2 years ago
if purge == nil then
purge = None
2 years ago
end
2 years ago
return purge
2 years ago
end)
2 years ago
local KickTarget = Bastion.UnitManager:CreateCustomUnit('kick', function(unit)
local kick = nil
2 years ago
2 years ago
Bastion.UnitManager:EnumEnemies(function(unit)
if unit:IsDead() then
return false
end
2 years ago
2 years ago
if not Player:CanSee(unit) then
return false
end
2 years ago
2 years ago
if Player:GetDistance(unit) > 40 then
return false
end
2 years ago
2 years ago
if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalKick(unit, 5) then
kick = unit
return true
end
end)
2 years ago
2 years ago
if kick == nil then
kick = None
2 years ago
end
2 years ago
return kick
2 years ago
end)
2 years ago
local StunTarget = Bastion.UnitManager:CreateCustomUnit('stun', function(unit)
local stun = nil
2 years ago
2 years ago
Bastion.UnitManager:EnumEnemies(function(unit)
if unit:IsDead() then
return false
end
2 years ago
2 years ago
if not Player:CanSee(unit) then
return false
end
2 years ago
2 years ago
if Player:GetDistance(unit) > 40 then
return false
end
2 years ago
2 years ago
if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalStun(unit, 5) then
stun = unit
return true
end
end)
2 years ago
2 years ago
if stun == nil then
stun = None
2 years ago
end
2 years ago
return stun
2 years ago
end)
2 years ago
local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit)
local tank = nil
2 years ago
2 years ago
Bastion.UnitManager:EnumFriends(function(unit)
if Player:GetDistance(unit) > 40 then
return false
end
2 years ago
if not Player:CanSee(unit) then
return false
end
2 years ago
if unit:IsDead() then
return false
end
2 years ago
if unit:IsTank() then
tank = unit
return true
end
return false
2 years ago
end)
2 years ago
if tank == nil then
tank = None
end
2 years ago
return tank
end)
2 years ago
local RuptureTarget = Bastion.UnitManager:CreateCustomUnit('rupture', function()
local target = nil
2 years ago
2 years ago
Bastion.UnitManager:EnumEnemies(function(unit)
if unit:IsDead() then
return false
end
2 years ago
2 years ago
if not Player:CanSee(unit) then
return false
end
2 years ago
if not Player:InMelee(unit) then
return false
end
2 years ago
if not Player:IsFacing(unit) then
return false
end
2 years ago
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)
2 years ago
if target == nil then
target = None
end
2 years ago
return target
end)
2 years ago
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
2 years ago
function WasInterruptUsedWithin(time)
local timeSinceKick = Kick:GetTimeSinceLastCastAttempt()
local timeSinceKidney = KidneyShot:GetTimeSinceLastCastAttempt()
local timeSinceGouge = Gouge:GetTimeSinceLastCastAttempt()
local timeSinceBlind = Blind:GetTimeSinceLastCastAttempt()
2 years ago
return timeSinceKick < time or timeSinceKidney < time or timeSinceGouge < time or timeSinceBlind < time
end
2 years ago
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,
}
2 years ago
local lastFeintTime = 0
2 years ago
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
2 years ago
end)
2 years ago
2 years ago
-- 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)
2 years ago
)
2 years ago
DefensivesAPL:AddSpell(
CrimsonVial:CastableIf(function(self)
return self:IsKnownAndUsable() and
not Player:IsCastingOrChanneling() and
Player:GetHealthPercent() < 70 and
not WasSelfHealUsedWithin(5)
end):SetTarget(Player)
2 years ago
)
2 years ago
DefensivesAPL:AddItem(
Healthstone:UsableIf(function(self)
return self:IsEquippedAndUsable() and
not Player:IsCastingOrChanneling() and
Player:GetHealthPercent() < 40 and
not WasSelfHealUsedWithin(5)
end):SetTarget(Player)
2 years ago
)
2 years ago
DefensivesAPL:AddItem(
RefreshingHealingPotion:UsableIf(function(self)
return self:IsEquippedAndUsable() and
not Player:IsCastingOrChanneling() and
Player:GetHealthPercent() < 40 and
not WasSelfHealUsedWithin(5)
end):SetTarget(Player)
2 years ago
)
2 years ago
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)
2 years ago
)
2 years ago
-- DefensivesAPL:AddSpell(
-- Evasion:CastableIf(function(self)
-- return self:IsKnownAndUsable() and
-- not Player:IsCastingOrChanneling() and
-- Player:GetHealthPercent() < 40
-- end):SetTarget(Player)
-- )
2 years ago
2 years ago
TrinketsAPL:AddItem(
IrideusFragment:UsableIf(function(self)
return self:IsEquippedAndUsable() and
not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss())
end):SetTarget(Player)
)
2 years ago
TrinketsAPL:AddItem(
ElementalPotionOfPower:UsableIf(function(self)
return self:IsEquippedAndUsable() and
not Player:IsCastingOrChanneling() and (Player:GetMeleeAttackers() > 2 or Target:IsBoss())
end):SetTarget(Player)
2 years ago
)
2 years ago
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)
2 years ago
)
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)
)
2 years ago
2 years ago
DefaultAPL:AddSpell(
2 years ago
Stealth:CastableIf(
function(self)
2 years ago
return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(Stealth):IsUp() and
not Player:IsAffectingCombat() and not IsMounted()
end
):SetTarget(Player)
2 years ago
)
DefaultAPL:AddSpell(
2 years ago
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)
2 years ago
)
DefaultAPL:AddSpell(
2 years ago
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)
2 years ago
)
DefaultAPL:AddSpell(
2 years ago
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)
2 years ago
)
2 years ago
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)
2 years ago
)
2 years ago
-- 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)
-- )
2 years ago
-- 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)
)
2 years ago
--[[
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
2 years ago
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
2 years ago
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
2 years ago
local function OpenerAbortCondition(spell)
-- If the cooldown is over 2 seconds we should abort the opener
return spell:GetCooldownRemaining() > 2
end
2 years ago
local OpenerSequenceST = Bastion.Sequencer:New({
function(self)
if OpenerAbortCondition(Shadowstrike) then
self:Abort()
print("Aborting on Shadowstrike")
return true
end
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
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
2 years ago
if Shadowstrike:IsKnownAndUsable() then
Shadowstrike:ForceCast(Target)
return true
end
return false
end,
}, ShouldResetSTOpener)
2 years ago
--[[
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
2 years ago
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
2 years ago
2 years ago
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
2 years ago
2 years ago
local OpenerSequenceAOE = Bastion.Sequencer:New({
function(self)
2 years ago
if OpenerAbortCondition(Shadowstrike) then
self:Abort()
print("Aborting on Shadowstrike")
return true
end
2 years ago
2 years ago
if Shadowstrike:IsKnownAndUsable() then
Shadowstrike:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(SymbolsOfDeath) then
self:Abort()
print("Aborting on SymbolsOfDeath")
return true
end
2 years ago
2 years ago
if SymbolsOfDeath:IsKnownAndUsable() then
SymbolsOfDeath:ForceCast(Player)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(ShadowBlades) then
self:Abort()
print("Aborting on ShadowBlades")
return true
end
2 years ago
if ShadowBlades:IsKnownAndUsable() then
ShadowBlades:ForceCast(Player)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(ShurikenTornado) then
self:Abort()
print("Aborting on ShurikenTornado")
return true
end
2 years ago
2 years ago
if ShurikenTornado:IsKnownAndUsable() then
ShurikenTornado:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(ShadowDance) then
self:Abort()
print("Aborting on ShadowDance")
return true
end
2 years ago
2 years ago
if ShadowDance:IsKnownAndUsable() then
ShadowDance:ForceCast(Player)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(ThistleTea) then
self:Abort()
print("Aborting on ThistleTea")
return true
end
2 years ago
2 years ago
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
2 years ago
if Rupture:IsKnownAndUsable() then
Rupture:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(BlackPowder) then
self:Abort()
print("Aborting on BlackPowder")
return true
end
2 years ago
2 years ago
if BlackPowder:IsKnownAndUsable() then
BlackPowder:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(SecretTechnique) then
self:Abort()
print("Aborting on SecretTechnique")
print("Aborting on ColdBlood")
return true
end
2 years ago
2 years ago
if SecretTechnique:IsKnownAndUsable() then
SecretTechnique:ForceCast(Target)
ColdBlood:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(BlackPowder) then
self:Abort()
print("Aborting on BlackPowder")
return true
end
2 years ago
2 years ago
if BlackPowder:IsKnownAndUsable() then
BlackPowder:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(Shadowstrike) then
self:Abort()
print("Aborting on Shadowstrike")
return true
end
2 years ago
2 years ago
if Shadowstrike:IsKnownAndUsable() then
Shadowstrike:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(BlackPowder) then
self:Abort()
print("Aborting on BlackPowder")
return true
end
2 years ago
2 years ago
if BlackPowder:IsKnownAndUsable() then
BlackPowder:ForceCast(Target)
return true
end
return false
end,
function(self)
2 years ago
if OpenerAbortCondition(ShurikenStorm) then
self:Abort()
print("Aborting on ShurikenStorm")
return true
end
2 years ago
if ShurikenStorm:IsKnownAndUsable() then
ShurikenStorm:ForceCast(Target)
return true
end
return false
end,
}, ShouldResetAOEOpener)
2 years ago
--[[
== Subtlety Rogue Concepts ==
2 years ago
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
2 years ago
-- 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)
2 years ago
if es > 3 then return false end
2 years ago
2 years ago
if power > 80 then return false end
2 years ago
if SD < 3 and power < 80 then
return true
end
2 years ago
return false
end
2 years ago
2 years ago
-- Gloomblade/Backstab: outside of Shadow Dance.
BuildersAPL:AddSpell(
Gloomblade:CastableIf(function(self)
return self:IsKnownAndUsable() and
not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and
(Player:GetEnemies(10) <= 1 or (Player:GetEnemies(10) <= 2 and Player:GetAuras():FindMy(LingeringShadow):GetRemainingTime() > 6)) and
self:IsInRange(Target)
2 years ago
and not ShouldPoolForCD()
end):SetTarget(Target)
)
2 years ago
-- BuildersAPL:AddSpell(
-- Backstab:CastableIf(function(self)
-- return self:IsKnownAndUsable() and
-- not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(ShadowDanceAura):IsUp()
-- end):SetTarget(Target)
-- )
2 years ago
2 years ago
-- 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)
2 years ago
)
2 years ago
-- 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)
2 years ago
)
2 years ago
--[[
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.
2 years ago
]]
-- 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)
2 years ago
)
2 years ago
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)
2 years ago
)
2 years ago
-- 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)
-- )
2 years ago
2 years ago
-- 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)
2 years ago
)
2 years ago
-- 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)
2 years ago
)
2 years ago
-- 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)
2 years ago
)
2 years ago
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)
)
2 years ago
-- 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
2 years ago
end)
)
2 years ago
-- Shadow Blades: on cooldown.
CDsAPL:AddSpell(
ShadowBlades:CastableIf(function(self)
return self:IsKnownAndUsable() and
not Player:IsCastingOrChanneling() and not IsDeadge(Target)
end):SetTarget(Player)
)
2 years ago
-- Vanish: After Secret Technique during Shadow Dance
CDsAPL:AddSpell(
Vanish:CastableIf(function(self)
return self:IsKnownAndUsable() and
not Player:IsCastingOrChanneling() and
Player:GetAuras():FindMy(ShadowDanceAura):IsUp() and
SecretTechnique:GetTimeSinceLastCast() <= 3 and Vanish:GetCharges() > 1 and Player:GetEnemies(10) < 7 and
GlobalCooldown:GetCooldownRemaining() <= 0
end):SetTarget(Player):OnCast(function()
Shadowstrike:ForceCast(Target)
end)
)
2 years ago
local function HasRampedInDance()
if ShadowDance:GetTimeSinceLastCast() > 8 then
return false
end
2 years ago
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)
)
2 years ago
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)
)
2 years ago
-- Cold Blood: before using a finishing move / before Secret Technique (if talented).
CDsAPL:AddSpell(
ColdBlood:CastableIf(function(self)
return (self:IsKnownAndUsable() or (SecretTechnique:IsKnownAndUsable() and ColdBlood:GetCooldownRemaining() > 2)) and
2 years ago
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)
)
2 years ago
-- 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)
)
2 years ago
-- DefaultAPL:AddSequence(
-- OpenerSequenceST,
-- ShouldPerformSTOpener
-- )
2 years ago
-- DefaultAPL:AddSequence(
-- OpenerSequenceAOE,
-- ShouldPerformAOEOpener
-- )
2 years ago
-- DefaultAPL:AddAPL(
-- OpenersAPL,
-- function()
-- return ShouldPerformSTOpener() or ShouldPerformAOEOpener()
-- end
-- )
2 years ago
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)
)
2 years ago
DefaultAPL:AddAPL(
DefensivesAPL,
function()
2 years ago
return true
end
)
2 years ago
DefaultAPL:AddAPL(
ItemsAPL,
function()
2 years ago
return true
end
)
2 years ago
DefaultAPL:AddAPL(
TrinketsAPL,
function()
return Player:GetAuras():FindMy(SymbolsOfDeath):IsUp() and not IsDeadge(Target)
end
)
2 years ago
DefaultAPL:AddAPL(
CDsAPL,
function() -- trying out a way to hold CDs, maybe it's dog
return Player:IsAffectingCombat() and not IsShiftKeyDown()
end
)
2 years ago
-- Use Finishing moves with 6 or more combo points (5 or more during Shadow Dance) with the following priority:
DefaultAPL:AddAPL(
FinishersAPL,
function()
return Player:GetComboPoints() >= 6 or
(Player:GetComboPoints() >= 5 and Player:GetAuras():FindMy(ShadowDanceAura):IsUp()) or
(Player:GetEnemies(10) > 3 and Player:GetComboPoints() >= 4)
2 years ago
end
)
2 years ago
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)
)
2 years ago
-- 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)
2 years ago
end
)
SubModulue:Sync(function()
2 years ago
if Player:IsAffectingCombat() and not IsCurrentSpell(6603) then
StartAttack()
end
-- DefensivesAPL:Execute()
-- InterruptAPL:Execute()
DefaultAPL:Execute()
2 years ago
end)
Bastion:Register(SubModulue)
2 years ago
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)