Compare commits

..

68 Commits
main ... main

Author SHA1 Message Date
4n0n c0c8ab8362 Fix? 11 months ago
4n0n fad3d76d8a 11.0 ItemName 11 months ago
4n0n e6b2b8f42f Merge pull request '11.0' (#27) from 4n0n/Bastion:main into main 11 months ago
4n0n 86e7aeccec 11.0 11 months ago
4n0n f9583a85d8 11.0 11 months ago
4n0n d0cad5746d 11.0 11 months ago
4n0n 010d2b4111 11.0 11 months ago
4n0n 7442498326 11.0 11 months ago
4n0n 83086a9b57 11.0 11 months ago
4n0n 878d5db7ae Merge pull request 'fix: C_UnitAuras.GetAuraDataByAuraInstanceID' (#21) from vibe/Bastion:fix_getauradatabyaurainstanceid into main 11 months ago
João Fidalgo 4d08a04958 fix: C_UnitAuras.GetAuraDataByAuraInstanceID 1 year ago
4n0n a22aa67d2a Merge pull request 'main' (#19) from CiscOH/Bastion:main into main 2 years ago
Ciscoh Bellic 3877c58b0c Removed return in load(dir) function. it was only loading the first file and no more 2 years ago
CiscOH 8a6308c584 Merge pull request 'main' (#7) from Bastion/Bastion:main into main 2 years ago
4n0n b43166be76 Era 2 years ago
4n0n ede085dd13 Merge pull request 'Bug Fix on APL traits' (#18) from LyLoLoq/Bastion:main into main 2 years ago
LyLoLoq 15920cecb3 Typing fix 2 years ago
LyLoLoq 8e5268229a Bug Fix on APL traits 2 years ago
CiscOH b1d34f113d Merge pull request 'main' (#6) from Bastion/Bastion:main into main 2 years ago
4n0n cadb7df64e Merge pull request 'Fix APL + Change to Spell:Cast' (#17) from LyLoLoq/Bastion:main into main 2 years ago
LyLoLoq a84930cef2 Change Spell:Cast 2 years ago
LyLoLoq 1bdf96e5ca Fix APL 2 years ago
CiscOH 4680344e6f Merge pull request 'main' (#5) from Bastion/Bastion:main into main 2 years ago
4n0n f722d17306 Merge branch 'main' of https://git.tinkr.site/Bastion/Bastion 2 years ago
4n0n 5db2f059d8 Add Spell:Fresh() 2 years ago
4n0n 910387c775 Merge pull request 'main' (#16) from CiscOH/Bastion:main into main 2 years ago
CiscOH 2994e8d1dd remove UnitManager:Validate(token) method 2 years ago
CiscOH eab3bb3a8a Merge pull request 'main' (#4) from Bastion/Bastion:main into main 2 years ago
4n0n e4d4270b60 add bastion requires 2 years ago
4n0n 025c0c5c88 remove 2 years ago
CiscOH 5dc90ce113 Merge pull request 'main' (#3) from Bastion/Bastion:main into main 2 years ago
4n0n bfdd70359d Fix typo 2 years ago
4n0n 5472aa1998 Merge pull request 'main' (#15) from CiscOH/Bastion:main into main 2 years ago
CiscOH ba9de7753f added line break to dump spells 2 years ago
CiscOH 94b584a99b Merge pull request 'Update 'README.md'' (#1) from Bastion/Bastion:main into main 2 years ago
4n0n 6924780785 Update 'README.md' 2 years ago
4n0n febee39182 Add highest/leastof functions to auratable, remove print 2 years ago
4n0n a99e71dd8e Aura table updates 2 years ago
4n0n 36ab641a13 Merge branch 'main' of https://git.tinkr.site/Bastion/Bastion 2 years ago
4n0n e8913d3ab8 Spellbook get list 2 years ago
4n0n 9d594366dd Merge pull request 'Change Unit.ttd_ticker initialization state' (#13) from abunai/Bastion:abunai-ttd-fix into main 2 years ago
abunai 2200a36bdf Change Unit.ttd_ticker initialization state 2 years ago
4n0n 559c4339a4 Fix aura list finding 2 years ago
4n0n 7b3b714d39 Add findanyof 2 years ago
4n0n 1cc8b541f8 Cleanup 2 years ago
4n0n 677229738d Move spellbook and itembook out of globals, remove prints 2 years ago
4n0n 9d47758f8c Merge branch 'main' of https://git.tinkr.site/Bastion/Bastion 2 years ago
4n0n 06916f6bc6 Spellbook and Itembook no longer shared through the engine 2 years ago
4n0n 4c138f1c9f Merge pull request 'More types' (#11) from lyloloq/Bastion:main into main 2 years ago
4n0n f5367327e6 Updated library handling 2 years ago
LyLoLoq 630fcfc3d9 More types 2 years ago
4n0n d5b0ccf42b Merge branch 'main' of https://git.tinkr.site/Bastion/Bastion 2 years ago
4n0n 5f0affd907 Add Bastion Libraries 2 years ago
4n0n 356cc78dd1 Merge pull request 'Remove rogue print' (#9) from 4n0n-patch-1 into main 2 years ago
4n0n 5bfc4f5097 Remove rogue print 2 years ago
4n0n 031e7e302d Merge pull request 'Fix Types + Changed `dumpspells` command' (#8) from lyloloq/Bastion:main into main 2 years ago
LyLoLoq 8cf6ffcb8e Fix types 2 years ago
4n0n 596e72d80a Merge pull request 'main' (#7) from CiscOH/Bastion:main into main 2 years ago
Ciscoh Bellic 81f7e371cd readded gitkeep 2 years ago
Ciscoh Bellic 728d427e41 empowered stage check 2 years ago
Ciscoh Bellic c634b58a38 ... 2 years ago
4n0n e3d52b41c4 Methods 2 years ago
4n0n fae312acfc Scripts gitkeep 2 years ago
4n0n 241d79d229 Merge branch 'main' of https://git.tinkr.site/4n0n/bastion 2 years ago
4n0n 147549310c Updates to bastion 2 years ago
4n0n da7ca9b082 Merge pull request 'main' (#4) from CiscOH/Bastion:main into main 2 years ago
OverHealz Jenkins f019304f81 added enemies w/most enemies. enemies centroid 2 years ago
CiscOH 72c4c8b87b Merge pull request 'main' (#1) from Bastion/Bastion:main into main 2 years ago
  1. 9
      .gitignore
  2. 4
      README.md
  3. 188
      references/brewmaster.lua
  4. 954
      references/outlaw.lua
  5. 226
      references/paladin/paladin_protection.lua
  6. 69
      references/paladin/shared/hammer-of-wrath.lua
  7. 57
      references/paladin/shared/rebuke.lua
  8. 758
      references/restodruid.lua
  9. 1379
      references/subtlety.lua
  10. 0
      references/windwalker.lua
  11. 0
      scripts/.gitkeep
  12. 24
      scripts/ExampleModule.lua
  13. 21
      scripts/Libraries/ExampleDependency.lua
  14. 15
      scripts/Libraries/ExampleDependencyError.lua
  15. 25
      scripts/Libraries/ExampleLibrary.lua
  16. 54
      scripts/arcane.lua
  17. 54
      scripts/fire.lua
  18. 59
      scripts/frost/frost-gui.lua
  19. 206
      scripts/frost/frost.lua
  20. 45
      scripts/shared/draw-rop.lua
  21. 25
      scripts/shared/get-ranged-target-count.lua
  22. 67
      src/APL/APL.lua
  23. 54
      src/Aura/Aura.lua
  24. 377
      src/AuraTable/AuraTable.lua
  25. 15
      src/Class/Class.lua
  26. 7
      src/Command/Command.lua
  27. 201
      src/ContentUtils/ContentUtils.lua
  28. 193
      src/ContentUtils/lists/important-aura-list.lua
  29. 662
      src/ContentUtils/lists/interrupt-list.lua
  30. 416
      src/ContentUtils/lists/tank-buster-list.lua
  31. 49
      src/Item/Item.lua
  32. 115
      src/Library/Library.lua
  33. 26
      src/List/List.lua
  34. 585
      src/MythicPlusUtils/MythicPlusUtils.lua
  35. 8
      src/ObjectManager/ObjectManager.lua
  36. 6
      src/Sequencer/Sequencer.lua
  37. 203
      src/Spell/Spell.lua
  38. 17
      src/SpellBook/SpellBook.lua
  39. 104
      src/ToggleManager/ToggleManager.lua
  40. 297
      src/Unit/Unit.lua
  41. 127
      src/UnitManager/UnitManager.lua
  42. 46
      src/Vector3/Vector3.lua
  43. 462
      src/_bastion.lua

9
.gitignore vendored

@ -6,7 +6,14 @@ DS_Store
!.gitkeep !.gitkeep
## ignore all files in scripts ## ignore all files in scripts
# scripts/* scripts/*
!scripts/Libraries
scripts/Libraries/*
!scripts/.gitkeep
!scripts/ExampleModule.lua
!scripts/Libraries/ExampleLibrary.lua
!scripts/Libraries/ExampleDependency.lua
!scripts/Libraries/ExampleDependencyError.lua
## ignore vscode settings ## ignore vscode settings
.vscode/* .vscode/*

@ -11,3 +11,7 @@ Feel free to browse around the [Wiki](https://git.tinkr.site/4n0n/bastion/wiki)
- [Download](https://git.tinkr.site/4n0n/bastion/archive/main.zip) or clone the repository. - [Download](https://git.tinkr.site/4n0n/bastion/archive/main.zip) or clone the repository.
- Move the bastion folder to your `Tinkr/scripts` folder. - Move the bastion folder to your `Tinkr/scripts` folder.
- Once in-game type `/bastion toggle` to enable the engine. - Once in-game type `/bastion toggle` to enable the engine.
## Developer Information
- [Issue Template](https://git.tinkr.site/Bastion/Bastion/wiki/Issues)
- [Pull Requests](https://git.tinkr.site/Bastion/Bastion/wiki/Pull-Requests)

@ -1,188 +0,0 @@
local Tinkr, _Bastion = ...
---@type Bastion
local Bastion = _Bastion
local BrewModule = Bastion.Module:New('brewmaster')
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local PurifyingBrew = Bastion.SpellBook:GetSpell(119582)
local CelestialBrew = Bastion.SpellBook:GetSpell(322507)
local PurifiedChi = Bastion.SpellBook:GetSpell(325092)
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local ImpCelestialBrew = Bastion.SpellBook:GetSpell(322510)
local ChiWave = Bastion.SpellBook:GetSpell(115098)
local RushingJadeWind = Bastion.SpellBook:GetSpell(116847)
local BreathOfFire = Bastion.SpellBook:GetSpell(115181)
local KegSmash = Bastion.SpellBook:GetSpell(121253)
local WeaponsOfOrder = Bastion.SpellBook:GetSpell(387184)
local BlackoutKick = Bastion.SpellBook:GetSpell(205523)
local RisingSunKick = Bastion.SpellBook:GetSpell(107428)
local TigerPalm = Bastion.SpellBook:GetSpell(100780)
local SpinningCraneKick = Bastion.SpellBook:GetSpell(322729)
local ExpelHarm = Bastion.SpellBook:GetSpell(322101)
local GiftOfTheOx = Bastion.SpellBook:GetSpell(124502)
local CDsEnabled = false
local Command = Bastion.Command:New('brewmaster')
Command:Register('cooldowns', 'Toggle Brewmaster CDs', function()
CDsEnabled = not CDsEnabled
Bastion:Print('Brewmaster Cooldowns ' .. (CDsEnabled and 'enabled' or 'disabled'))
end)
---@return number | nil
local function GetExpelHarmHealAmount()
-- Gift of the Ox orbs
local OrbCount = GetSpellCount(ExpelHarm:GetID()) or 0
local OrbHeal = tonumber((GetSpellDescription(GiftOfTheOx:GetID()):match("%d+%S+%d"):gsub("%D","")))
print(GetSpellDescription(GiftOfTheOx:GetID()):match("%d+%S+%d"))
-- expel harm
local ExpelHeal = tonumber((GetSpellDescription(ExpelHarm:GetID()):match("%d+%S+%d"):gsub("%D","")))
return ExpelHeal + (OrbHeal * OrbCount)
end
---@return boolean
local function CombatRotation()
local isAoe = Player:GetEnemies(8) > 1
-- Attack the target is auto attack isnt active.
if AutoAttack:IsKnownAndUsable() and not IsCurrentSpell(AutoAttack:GetID()) and Player:InMelee(Target) and not Target:IsDead() then
AutoAttack:Cast(Target)
end
local Stagger = UnitStagger("player")
if Stagger > 10000 and PurifyingBrew:IsKnownAndUsable() and PurifyingBrew:GetChargesRemaining() > 1 or (PurifyingBrew:GetNextChargeCooldown() <= 2.5 and PurifyingBrew:GetChargesRemaining() == 1) then
PurifyingBrew:Cast(Player)
end
if CelestialBrew:IsKnownAndUsable() and (not ImpCelestialBrew:IsKnown() or (ImpCelestialBrew:IsKnown() and Player:GetAuras():FindMy(PurifiedChi):IsUp())) then
return CelestialBrew:Cast(Player)
end
local ExpelHarmHeal = GetExpelHarmHealAmount()
if not Player:InMelee(Target) then
if not Player:GetAuras():FindMy(RushingJadeWind):IsUp() and RushingJadeWind:IsKnownAndUsable() then
return RushingJadeWind:Cast(Player)
end
if ChiWave:IsKnownAndUsable() and Target:Exists() and ChiWave:IsInRange(Target) and Player:IsFacing(Target) then
return ChiWave:Cast(Target)
end
end
if BreathOfFire:IsKnownAndUsable() and BreathOfFire:IsInRange(Target) and Player:IsFacing(Target) then
return BreathOfFire:Cast(Player)
end
if KegSmash:IsKnownAndUsable() and Target:Exists() and KegSmash:IsInRange(Target) and Player:IsFacing(Target) then
if KegSmash:GetChargesRemaining() == KegSmash:GetMaxCharges() then
return KegSmash:Cast(Target)
end
if KegSmash:GetChargesRemaining() == KegSmash:GetMaxCharges() - 1 and KegSmash:GetNextChargeCooldown() <= 3 then
return KegSmash:Cast(Target)
end
end
if WeaponsOfOrder:IsKnownAndUsable() and CDsEnabled then
return WeaponsOfOrder:Cast(Player)
end
-- Exploding Keg in AOE here
-- Rising Sun Kick In AOE here
if BlackoutKick:IsKnownAndUsable() and Target:Exists() and BlackoutKick:IsInRange(Target) and Player:IsFacing(Target) then
return BlackoutKick:Cast(Target)
end
if RisingSunKick:IsKnownAndUsable() and Target:Exists() and RisingSunKick:IsInRange(Target) and Player:IsFacing(Target) then
return RisingSunKick:Cast(Target)
end
-- Exploding Keg in ST here
if ChiWave:IsKnownAndUsable() and Target:Exists() and ChiWave:IsInRange(Target) then
return ChiWave:Cast(Target)
end
if RushingJadeWind:IsKnownAndUsable() and (not Player:GetAuras():FindMy(RushingJadeWind):IsUp() or Player:GetAuras():FindMy(RushingJadeWind):GetRemainingTime() <= 2) then
return RushingJadeWind:Cast(Player)
end
if not isAoe and TigerPalm:IsKnownAndUsable() and Target:Exists() and TigerPalm:IsInRange(Target) and Player:IsFacing(Target) then
if not KegSmash:IsKnown() then
return TigerPalm:Cast(Target)
else
local TigerPalmCost = GetSpellPowerCost(TigerPalm:GetID())[1]['cost']
local KegSmashCost = GetSpellPowerCost(KegSmash:GetID())[1]['cost']
local CurrentEnergy = Player:GetPower(Enum.PowerType.Energy)
local CanKegSmash = KegSmash:GetChargesRemaining() > 0 or KegSmash:GetNextChargeCooldown() >= 2.5
if CanKegSmash then
if (CurrentEnergy - KegSmashCost) >= TigerPalmCost then
return TigerPalm:Cast(Target)
end
else
return TigerPalm:Cast(Target)
end
end
end
if isAoe and SpinningCraneKick:IsKnownAndUsable() then
if not KegSmash:IsKnown() then
return SpinningCraneKick:Cast(Player)
else
local SpinningCraneKickCost = GetSpellPowerCost(SpinningCraneKick:GetID())[1]['cost']
local KegSmashCost = GetSpellPowerCost(KegSmash:GetID())[1]['cost']
local CurrentEnergy = Player:GetPower(Enum.PowerType.Energy)
local CanKegSmash = KegSmash:GetChargesRemaining() > 0 or KegSmash:GetNextChargeCooldown() >= 2.5
if CanKegSmash then
if (CurrentEnergy - KegSmashCost) >= SpinningCraneKickCost then
return SpinningCraneKick:Cast(Player)
end
else
return SpinningCraneKick:Cast(Player)
end
end
end
return false
end
---@return boolean
local function OutOfCombatRotation()
return false
end
local isRunning = false
BrewModule:Sync(function()
if not isRunning then
Bastion:Print('Brewmaster Started')
isRunning = true
end
if not Player:IsAlive() or Player:IsMounted() then
return false
end
if Player:IsAffectingCombat() or IsCurrentSpell(AutoAttack:GetID()) then
-- Combat Rotation
return CombatRotation()
else
-- Out Of Combat Rotation
return OutOfCombatRotation()
end
end)
Bastion:Register(BrewModule)

@ -1,954 +0,0 @@
-- 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)<?(6-talent.summarily_dispatched))|effective_combo_points>=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<int>( 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<gcd
-- CDsAPL:AddSpell(
-- BladeFlurry:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and Target:GetMeleeAttackers() >= 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<combo_points.deficit|combo_points.deficit>=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<charges*6)
-- CDsAPL:AddSpell(
-- ThistleTea:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and Player:GetAuras():FindMy(ThistleTea):IsDown() and
-- (Player:GetPowerDeficit() >= 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<fight_remains&refreshable&(!talent.swift_slasher|combo_points>=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.audacity<talent.count_the_odds+talent.keep_it_rolling
-- StealthAPL:AddVariable(
-- 'vanish_opportunity_condition',
-- function()
-- return not ShadowDance:IsKnown() and
-- (FanTheHammer:IsKnown() and 1 or 0) + (QuickDraw:IsKnown() and 1 or 0) + (Audacity:IsKnown() and 1 or 0) <
-- (CountTheOdds:IsKnown() and 1 or 0) + (KeepItRolling:IsKnown() and 1 or 0)
-- end
-- )
-- -- actions.stealth_cds+=/vanish,if=talent.find_weakness&!talent.audacity&debuff.find_weakness.down&variable.ambush_condition&variable.vanish_condition
-- StealthAPL:AddSpell(
-- Vanish:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and FindWeakness:IsKnown() and not Audacity:IsKnown() and
-- not Target:GetAuras():FindMy(FindWeakness):IsUp() and
-- DefaultAPL:GetVariable('ambush_condition') and StealthAPL:GetVariable('vanish_condition')
-- end
-- ):SetTarget(Target)
-- )
-- -- actions.stealth_cds+=/vanish,if=talent.hidden_opportunity&!buff.audacity.up&(variable.vanish_opportunity_condition|buff.opportunity.stack<buff.opportunity.max_stack)&variable.ambush_condition&variable.vanish_condition
-- StealthAPL:AddSpell(
-- Vanish:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and HiddenOpportunity:IsKnown() and
-- not Player:GetAuras():FindMy(Audacity):IsUp() and
-- (StealthAPL:GetVariable('vanish_opportunity_condition') or
-- Player:GetAuras():FindMy(OpportunityAura):GetCount() <
-- MaxOpportunity()) and
-- DefaultAPL:GetVariable('ambush_condition') and StealthAPL:GetVariable('vanish_condition')
-- end
-- ):SetTarget(Target)
-- )
-- -- actions.stealth_cds+=/vanish,if=(!talent.find_weakness|talent.audacity)&!talent.hidden_opportunity&variable.finish_condition&variable.vanish_condition
-- StealthAPL:AddSpell(
-- Vanish:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and (not FindWeakness:IsKnown() or Audacity:IsKnown()) and
-- not HiddenOpportunity:IsKnown() and
-- DefaultAPL:GetVariable('finish_condition') and StealthAPL:GetVariable('vanish_condition')
-- end
-- ):SetTarget(Target)
-- )
-- -- actions.stealth_cds+=/variable,name=shadow_dance_condition,value=talent.shadow_dance&debuff.between_the_eyes.up&(!talent.ghostly_strike|debuff.ghostly_strike.up)&(!talent.dreadblades|!cooldown.dreadblades.ready)&(!talent.hidden_opportunity|!buff.audacity.up&(talent.fan_the_hammer.rank<2|!buff.opportunity.up))
-- StealthAPL:AddVariable(
-- 'shadow_dance_condition',
-- function()
-- return ShadowDance:IsKnown() and Target:GetAuras():FindMy(BetweenTheEyes):IsUp() and
-- (not GhostlyStrike:IsKnown() or Target:GetAuras():FindMy(GhostlyStrike):IsUp()) and
-- (not Dreadblades:IsKnown() or not Dreadblades:CooldownUp()) and
-- (not HiddenOpportunity:IsKnown() or not Player:GetAuras():FindMy(Audacity):IsUp() and
-- (1 < 2 or not Player:GetAuras():FindMy(OpportunityAura):IsUp()))
-- end
-- )
-- -- actions.stealth_cds+=/shadow_dance,if=!talent.keep_it_rolling&variable.shadow_dance_condition&buff.slice_and_dice.up&(variable.finish_condition|talent.hidden_opportunity)&(!talent.hidden_opportunity|!cooldown.vanish.ready)
-- StealthAPL:AddSpell(
-- ShadowDance:CastableIf(
-- function(self)
-- return self:IsKnownAndUsable() and not KeepItRolling:IsKnown() and
-- StealthAPL:GetVariable('shadow_dance_condition') and
-- Player:GetAuras():FindMy(SliceAndDice):IsUp() and
-- (DefaultAPL:GetVariable('finish_condition') or HiddenOpportunity:IsKnown()) and
-- (not HiddenOpportunity:IsKnown() or not Vanish:CooldownUp())
-- end
-- ):SetTarget(Target)
-- )
-- -- actions.stealth_cds+=/shadow_dance,if=talent.keep_it_rolling&variable.shadow_dance_condition&(cooldown.keep_it_rolling.remains<=30|cooldown.keep_it_rolling.remains>120&(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)

@ -1,226 +0,0 @@
local Tinkr, _Bastion = ...
---@type Bastion
local Bastion = _Bastion
local ProtModule = Bastion.Module:New('paladin_protection')
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local CrusaderAura = Bastion.SpellBook:GetSpell(32223)
local DevoAura = Bastion.SpellBook:GetSpell(465)
local Consecrate = Bastion.SpellBook:GetSpell(26573)
local ConsecrateAura = Bastion.SpellBook:GetSpell(188370)
local Judgement = Bastion.SpellBook:GetSpell(275779)
local AvengersShield = Bastion.SpellBook:GetSpell(31935)
local OfDuskAndDawn = Bastion.SpellBook:GetSpell(385125)
local BlessingOfTheDusk = Bastion.SpellBook:GetSpell(385126)
local BlessingOfTheDawn = Bastion.SpellBook:GetSpell(385127)
local ShieldOfTheRighteous = Bastion.SpellBook:GetSpell(53600)
local BlessedHammer = Bastion.SpellBook:GetSpell(204019)
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local WoG = Bastion.SpellBook:GetSpell(85673)
local ShinningLight = Bastion.SpellBook:GetSpell(327510)
local HolyPower = Enum.PowerType.HolyPower
---@type Rebuke
local Rebuke = Tinkr:require("scripts/bastion/scripts/paladin/shared/rebuke", Bastion)
---@type HammerOfWrath
local HammerOfWrath = Tinkr:require("scripts/bastion/scripts/paladin/shared/hammer-of-wrath", Bastion)
local AvengersShieldTarget = Bastion.UnitManager:CreateCustomUnit('avengersshield', 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:IsFacing(unit) then
return false
end
if not AvengersShield:IsInRange(unit) then
return false
end
if unit:IsCastingOrChanneling() and unit:IsInterruptibleAt(5) then
target = unit
return true
end
return false
end)
if target == nil then
if Target:Exists() then
target = Target
else
target = None
end
end
return target
end)
local WoGTarget = Bastion.UnitManager:CreateCustomUnit('wordofglory', function()
if not WoG:IsKnownAndUsable() then return None end
local lowest = nil
local lowestHP = math.huge
Bastion.UnitManager:EnumFriends(function(unit)
if unit:IsDead() then
return false
end
if WoG:IsInRange(unit) then
return false
end
if not Player:CanSee(unit) then
return false
end
if not unit:GetHealthPercent() <= 55 then
return false
end
local hp = unit:GetHP()
if hp < lowestHP then
lowest = unit
lowestHP = hp
end
return false
end)
if not lowest then
lowest = None
end
return lowest
end)
---@return boolean
local function CombatRotation()
-- Attack the target is auto attack isnt active.
if AutoAttack:IsKnownAndUsable() and not IsCurrentSpell(AutoAttack:GetID()) and Player:InMelee(Target) and not Target:IsDead() then
AutoAttack:Cast(Target)
end
-- Kick
Rebuke()
-- Cast Consecrate if an enemy is in range, and the player isnt moving.
if Consecrate:IsKnownAndUsable() and not Player:IsMoving() and not Player:GetAuras():FindMy(ConsecrateAura):IsUp() and Player:GetEnemies(10) >= 1 then
return Consecrate:Cast(Player)
end
if WoG:IsKnownAndUsable() and Player:GetAuras():FindMy(ShinningLight):IsUp() and Player:GetHealthPercent() <= 50 and not Player:IsCastingOrChanneling() then
return WoG:Cast(Player)
end
-- Handle SoR specially if DuskAndDawn is known.
if OfDuskAndDawn:IsKnown() then
local HpToSor = 3
if Player:GetAuras():FindMy(BlessingOfTheDusk):IsUp() and Player:GetAuras():FindMy(BlessingOfTheDawn):GetRemainingTime() < 9 then
HpToSor = 5
end
if Player:GetPower(HolyPower) > 3 then
HpToSor = 5
end
if Player:GetPower(HolyPower) == HpToSor and ShieldOfTheRighteous:IsKnownAndUsable() and ShieldOfTheRighteous:IsInRange(Target) and not Player:IsCastingOrChanneling() then
ShieldOfTheRighteous:Cast(Target)
end
else
-- If Dusk And Dawn isnt known, rip SoR normally.
if ShieldOfTheRighteous:IsKnownAndUsable() and ShieldOfTheRighteous:IsInRange(Target) and not Player:IsCastingOrChanneling() then
ShieldOfTheRighteous:Cast(Target)
end
end
-- Avengers shield with higher prio than Judgement in AOE. Prefer targets that are casting.
if Player:GetEnemies(10) > 1 and AvengersShield:IsKnownAndUsable() and AvengersShieldTarget:Exists() then
return AvengersShield:Cast(AvengersShieldTarget)
end
-- Judgement
if Judgement:IsKnownAndUsable() and Player:GetPower(HolyPower) < 5 and Target:Exists() and Judgement:IsInRange(Target) then
return Judgement:Cast(Target)
end
-- Hammer of Wrath
if HammerOfWrath() then
return true
end
-- Avengers shield with lower prio than Judgement in ST. Prefer targets that are casting.
if Player:GetEnemies(10) == 1 and AvengersShield:IsKnownAndUsable() and AvengersShieldTarget:Exists() then
return AvengersShield:Cast(AvengersShieldTarget)
end
-- Blessed Hammer if there are more than 1 enemy in 10 yds.
if BlessedHammer:IsKnownAndUsable() and Player:GetPower(HolyPower) < 5 and Player:GetEnemies(10) >= 1 then
return BlessedHammer:Cast(Player)
end
if Player:GetAuras():FindMy(ShinningLight):IsUp() and WoGTarget:Exists() and not Player:IsCastingOrChanneling() then
return WoG:Cast(WoGTarget)
end
-- Refresh Consecrate if we've got nothing else to cast.
if Consecrate:IsKnownAndUsable() and Player:GetEnemies(10) >= 1 and not Player:IsMoving() then
return Consecrate:Cast(Player)
end
return false
end
---@return boolean
local function OutOfCombatRotation()
return false
end
local isRunning = false
ProtModule:Sync(function()
if not isRunning then
Bastion:Print('Prot Paladin Started')
isRunning = true
end
if not Player:IsAlive() then
return false
end
-- Cast Crusader Aura if talented and the player is mounted.
if Player:IsMounted() and CrusaderAura:IsKnownAndUsable() and not Player:GetAuras():FindMy(CrusaderAura):IsUp() and not Player:IsCastingOrChanneling() then
return CrusaderAura:Cast(Player)
end
-- Cast Devo Aura if the player is not mounted.
if not Player:IsMounted() and DevoAura:IsKnownAndUsable() and not Player:GetAuras():FindMy(DevoAura):IsUp() and not Player:IsCastingOrChanneling() then
return DevoAura:Cast(Player)
end
if Player:IsAffectingCombat() then
-- Combat Rotation
return CombatRotation()
else
-- Out Of Combat Rotation
return OutOfCombatRotation()
end
end)
Bastion:Register(ProtModule)

@ -1,69 +0,0 @@
local Tinkr, _Bastion = ...
---@type Bastion
local Bastion = _Bastion
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local _HammerOfWrath = Bastion.SpellBook:GetSpell(24275)
local AvengingWrath = Bastion.SpellBook:GetSpell(389539)
local HammerOfWrathTarget = Bastion.UnitManager:CreateCustomUnit('hammerofwrath', function()
if not _HammerOfWrath:IsKnownAndUsable() then return None end
if Player:GetAuras():FindMy(AvengingWrath):IsUp() and _HammerOfWrath:IsKnownAndUsable() and Target:Exists() and Target:IsEnemy() and Target:IsAffectingCombat() and _HammerOfWrath:IsInRange(Target) then
return Target
end
local how = 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:IsFacing(unit) then
return false
end
if not _HammerOfWrath:IsInRange(unit) then
return false
end
if Player:GetAuras():FindMy(AvengingWrath):IsUp() then
how = unit
return true
end
if unit:GetHealthPercent() <= 20 then
how = unit
return true
end
return false
end)
if how == nil then
how = None
end
return how
end)
---@alias HammerOfWrath fun():boolean
---@type HammerOfWrath
function HammerOfWrath()
if HammerOfWrathTarget:Exists() and not Player:IsCastingOrChanneling() then
return _HammerOfWrath:Cast(HammerOfWrathTarget)
end
return false
end
return HammerOfWrath

@ -1,57 +0,0 @@
local Tinkr, _Bastion = ...
---@type Bastion
local Bastion = _Bastion
local Player = Bastion.UnitManager:Get('player')
local None = Bastion.UnitManager:Get('none')
local _Rebuke = Bastion.SpellBook:GetSpell(96231)
local RebukeTarget = Bastion.UnitManager:CreateCustomUnit('rebuke', function()
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 unit:IsInterruptibleAt(10) and _Rebuke:IsInRange(unit) and Player:IsFacing(unit) then
kick = unit
return true
end
-- TODO: Only consider this if the player is in an M+ dungeon
-- if Player:InMelee(unit) and Player:IsFacing(unit) and Bastion.MythicPlusUtils:CastingCriticalKick(unit, 5) then
-- kick = unit
-- return true
-- end
return false
end)
if kick == nil then
kick = None
end
return kick
end)
---@alias Rebuke fun()
---@type Rebuke
function Rebuke()
if RebukeTarget:Exists() and not Player:IsCastingOrChanneling() and _Rebuke:IsKnownAndUsable() then
_Rebuke:Cast(RebukeTarget)
end
end
return Rebuke

@ -1,758 +0,0 @@
local Tinkr, Bastion = ...
local RestoModule = Bastion.Module:New('resto_druid')
local Evaluator = Tinkr.Util.Evaluator
local Player = Bastion.UnitManager:Get('player')
local None = Bastion.UnitManager:Get('none')
local Target = Bastion.UnitManager:Get('target')
local AnomalyDetectionMarkI = Bastion.SpellBook:GetSpell(382499)
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local MechanismBypass = Bastion.SpellBook:GetSpell(382501)
local OverloadElementalDeposit = Bastion.SpellBook:GetSpell(388213)
local ReviveBattlePets = Bastion.SpellBook:GetSpell(125439)
local WarStomp = Bastion.SpellBook:GetSpell(20549)
local ArmorSkills = Bastion.SpellBook:GetSpell(76275)
local Brawn = Bastion.SpellBook:GetSpell(154743)
local Cultivation = Bastion.SpellBook:GetSpell(20552)
local Endurance = Bastion.SpellBook:GetSpell(20550)
local Languages = Bastion.SpellBook:GetSpell(79746)
local MasterRiding = Bastion.SpellBook:GetSpell(90265)
local NatureResistance = Bastion.SpellBook:GetSpell(20551)
local WeaponSkills = Bastion.SpellBook:GetSpell(76300)
local ActivateEmpowerment = Bastion.SpellBook:GetSpell(357857)
local BlessingofOhnara = Bastion.SpellBook:GetSpell(384522)
local BronzeTimelock = Bastion.SpellBook:GetSpell(374990)
local ChampionAbility = Bastion.SpellBook:GetSpell(356550)
local CenarionWard = Bastion.SpellBook:GetSpell(102351)
local CombatAlly = Bastion.SpellBook:GetSpell(211390)
local ConstructAbility = Bastion.SpellBook:GetSpell(347013)
local CovenantAbility = Bastion.SpellBook:GetSpell(313347)
local GarrisonAbility = Bastion.SpellBook:GetSpell(161691)
local HeartEssence = Bastion.SpellBook:GetSpell(296208)
local HuntingCompanion = Bastion.SpellBook:GetSpell(376280)
local SanityRestorationOrb = Bastion.SpellBook:GetSpell(314955)
local SignatureAbility = Bastion.SpellBook:GetSpell(326526)
local SkywardAscent = Bastion.SpellBook:GetSpell(372610)
local SummonPocopoc = Bastion.SpellBook:GetSpell(360078)
local SurgeForward = Bastion.SpellBook:GetSpell(372608)
local Throw = Bastion.SpellBook:GetSpell(385265)
local VenthyrAbility = Bastion.SpellBook:GetSpell(315594)
local WartimeAbility = Bastion.SpellBook:GetSpell(264739)
local WhirlingSurge = Bastion.SpellBook:GetSpell(361584)
local PocopocZoneAbilitySkill = Bastion.SpellBook:GetSpell(363942)
local DragonridingBasics = Bastion.SpellBook:GetSpell(376777)
local LiftOff = Bastion.SpellBook:GetSpell(383363)
local ThrilloftheSkies = Bastion.SpellBook:GetSpell(383366)
local Vigor = Bastion.SpellBook:GetSpell(383359)
local WindsoftheIsles = Bastion.SpellBook:GetSpell(373586)
local Barkskin = Bastion.SpellBook:GetSpell(22812)
local BearForm = Bastion.SpellBook:GetSpell(5487)
local CatForm = Bastion.SpellBook:GetSpell(768)
local Cyclone = Bastion.SpellBook:GetSpell(33786)
local EntanglingRoots = Bastion.SpellBook:GetSpell(339)
local FerociousBite = Bastion.SpellBook:GetSpell(22568)
local FrenziedRegeneration = Bastion.SpellBook:GetSpell(22842)
local Growl = Bastion.SpellBook:GetSpell(6795)
local Innervate = Bastion.SpellBook:GetSpell(29166)
local Mangle = Bastion.SpellBook:GetSpell(33917)
local MarkoftheWild = Bastion.SpellBook:GetSpell(1126)
local Moonfire = Bastion.SpellBook:GetSpell(8921)
local MoonfireAura = Bastion.SpellBook:GetSpell(164812)
local Prowl = Bastion.SpellBook:GetSpell(5215)
local Rebirth = Bastion.SpellBook:GetSpell(20484)
local Regrowth = Bastion.SpellBook:GetSpell(8936)
local Rejuvenation = Bastion.SpellBook:GetSpell(774)
local RejuvenationAura = Bastion.SpellBook:GetSpell(25299)
local Revive = Bastion.SpellBook:GetSpell(50769)
local Rip = Bastion.SpellBook:GetSpell(1079)
local Shred = Bastion.SpellBook:GetSpell(5221)
local Soothe = Bastion.SpellBook:GetSpell(2908)
local StampedingRoar = Bastion.SpellBook:GetSpell(106898)
local Sunfire = Bastion.SpellBook:GetSpell(93402)
local SunfireAura = Bastion.SpellBook:GetSpell(164815)
local Swiftmend = Bastion.SpellBook:GetSpell(18562)
local TeleportMoonglade = Bastion.SpellBook:GetSpell(18960)
local Thrash = Bastion.SpellBook:GetSpell(106832)
local TigerDash = Bastion.SpellBook:GetSpell(252216)
local TravelForm = Bastion.SpellBook:GetSpell(783)
local UrsolsVortex = Bastion.SpellBook:GetSpell(102793)
local WildGrowth = Bastion.SpellBook:GetSpell(48438)
local Wrath = Bastion.SpellBook:GetSpell(5176)
local AquaticForm = Bastion.SpellBook:GetSpell(276012)
local FlightForm = Bastion.SpellBook:GetSpell(276029)
local TigerDash = Bastion.SpellBook:GetSpell(252216)
local Efflorescence = Bastion.SpellBook:GetSpell(145205)
local IncarnationTreeofLife = Bastion.SpellBook:GetSpell(33891)
local Ironbark = Bastion.SpellBook:GetSpell(102342)
local Lifebloom = Bastion.SpellBook:GetSpell(33763)
local LifebloomAura = Bastion.SpellBook:GetSpell(188550)
local NaturesCure = Bastion.SpellBook:GetSpell(88423)
local NaturesSwiftness = Bastion.SpellBook:GetSpell(132158)
local Revitalize = Bastion.SpellBook:GetSpell(212040)
local Tranquility = Bastion.SpellBook:GetSpell(740)
local MasteryHarmony = Bastion.SpellBook:GetSpell(77495)
local Moonfire = Bastion.SpellBook:GetSpell(8921)
local Wrath = Bastion.SpellBook:GetSpell(5176)
local BearForm = Bastion.SpellBook:GetSpell(5487)
local AdaptiveSwarm = Bastion.SpellBook:GetSpell(391888)
local AdaptiveSwarmBuff = Bastion.SpellBook:GetSpell(391891)
local ClearCasting = Bastion.SpellBook:GetSpell(16870)
local ConvokeTheSpirits = Bastion.SpellBook:GetSpell(391528)
local Flourish = Bastion.SpellBook:GetSpell(197721)
local SoulOfTheForest = Bastion.SpellBook:GetSpell(114108)
local Bursting = Bastion.SpellBook:GetSpell(240443)
local Rake = Bastion.SpellBook:GetSpell(1822)
local RakeAura = Bastion.SpellBook:GetSpell(155722)
local Starsurge = Bastion.SpellBook:GetSpell(197626)
local NaturesVigil = Bastion.SpellBook:GetSpell(124974)
local SpringBlossoms = Bastion.SpellBook:GetSpell(207386)
local RakeDebuff = Bastion.SpellBook:GetSpell(155722)
local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit)
local lowest = nil
local lowestHP = math.huge
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
local hp = unit:GetHP()
if hp < lowestHP then
lowest = unit
lowestHP = hp
end
end)
if not lowest then
lowest = Player
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
if not Player:CanSee(unit) then
return false
end
if Player:GetDistance(unit) > 40 then
return false
end
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)
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 not unit:IsDead() and Player:CanSee(unit) and
unit:GetAuras():HasAnyStealableAura() 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 = Player
end
return tank
end)
local RejuvUnit = Bastion.UnitManager:CreateCustomUnit('rejuv', function(unit)
local lowest = nil
local lowestHP = math.huge
Bastion.UnitManager:EnumFriends(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 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
end
end
end)
if lowest == nil then
lowest = Player
end
return lowest
end)
local SwiftmendUnit = Bastion.UnitManager:CreateCustomUnit('swiftmend', function(unit)
local lowest = nil
local lowestHP = math.huge
Bastion.UnitManager:EnumFriends(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:CanSee(unit) and (
(unit:GetAuras():FindMy(Regrowth):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
end
end
end)
if lowest == nil then
lowest = None
end
return lowest
end)
local WildGrowthUnit = Bastion.UnitManager:CreateCustomUnit('wildgrowth', function(unit)
local lowest = nil
local lowestHP = math.huge
Bastion.UnitManager:EnumFriends(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:CanSee(unit) and (
(
Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and
(
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
end
end
end)
if lowest == nil then
lowest = None
end
return lowest
end)
local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit)
local explosive = nil
Bastion.ObjectManager.explosives:each(function(unit)
if unit:IsDead() then
return false
end
if not Player:CanSee(unit) then
return false
end
if Player:GetDistance(unit) <= 40 then
explosive = unit
return true
end
end)
if explosive == nil then
explosive = None
end
return explosive
end)
local RakeTarget = Bastion.UnitManager:CreateCustomUnit('rake', function(unit)
local rakeTarget = 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 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)
if rakeTarget == nil then
rakeTarget = None
end
return rakeTarget
end)
local MoonfireTarget = Bastion.UnitManager:CreateCustomUnit('moonfire', function(unit)
local moonfireTarget = 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 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
end
end)
if moonfireTarget == nil then
moonfireTarget = None
end
return moonfireTarget
end)
local SunfireTarget = Bastion.UnitManager:CreateCustomUnit('sunfire', function(unit)
local sunfireTarget = 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 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
end
end)
if sunfireTarget == nil then
sunfireTarget = None
end
return sunfireTarget
end)
local RestoCommands = Bastion.Command:New('resto')
local PLACE_EFFLO = false
RestoCommands:Register('efflo', 'Request the engine to place an Efflorescence', function()
PLACE_EFFLO = true
Bastion.Notifications:AddNotification(Efflorescence:GetIcon(), "Efflorescence requested")
end)
local DefaultAPL = Bastion.APL:New('default')
local DamageAPL = Bastion.APL:New('damage')
DamageAPL:AddSpell(
Rake:CastableIf(function(self)
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)
end):SetTarget(RakeTarget)
)
DamageAPL:AddSpell(
FerociousBite:CastableIf(function(self)
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
Player:GetComboPoints() >= 5
end):SetTarget(Target)
)
DamageAPL:AddSpell(
Shred:CastableIf(function(self)
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
Player:GetComboPoints() < 5
end):SetTarget(Target)
)
DefaultAPL:AddSpell(
Efflorescence:CastableIf(function(self)
return PLACE_EFFLO and Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
end):SetTarget(None):OnCast(function(self)
local loc = Bastion.UnitManager:FindFriendsCentroid(10, 40)
PLACE_EFFLO = false
self:Click(loc)
end)
)
CatForm:OnCast(function(self)
if not Player:GetAuras():FindMy(Prowl):IsUp() and not Player:IsAffectingCombat() then
Prowl:Cast(Player)
end
end)
DefaultAPL:AddAction(
'cat_form_shift',
function()
if (IsShiftKeyDown()) and not Player:IsMounted() and
not Player:GetAuras():FindMy(CatForm):IsUp() and
not Player:IsCastingOrChanneling() then
CatForm:Cast(Player)
elseif (not IsShiftKeyDown() and Player:IsAffectingCombat()) and Player:GetAuras():FindMy(CatForm):IsUp() then
CancelShapeshiftForm()
end
end
)
DefaultAPL:AddSpell(
NaturesCure:CastableIf(function(self)
return DispelTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
self:IsInRange(DispelTarget) and DispelTarget:GetAuras():HasAnyDispelableAura(NaturesCure)
end):SetTarget(DispelTarget)
)
DefaultAPL:AddSpell(
Soothe:CastableIf(function(self)
return PurgeTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
self:IsInRange(PurgeTarget) and PurgeTarget:GetAuras():HasAnyStealableAura()
end):SetTarget(PurgeTarget)
)
DefaultAPL:AddSpell(
NaturesSwiftness:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Lowest) and Lowest:GetHP() < 70
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
ConvokeTheSpirits:CastableIf(function(self)
return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
self:IsInRange(Player) and (Player:GetPartyHPAround(40, 70) >= 2 or Player:GetPartyHPAround(40, 75) >= 3)
and (Flourish:IsKnownAndUsable() or Flourish:GetTimeSinceLastCast() > 10)
end):SetTarget(Player)
)
DefaultAPL:AddSpell(
Flourish:CastableIf(function(self)
return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
self:IsInRange(Player) and (Player:GetPartyHPAround(40, 70) >= 2 or Player:GetPartyHPAround(40, 75) >= 3) and
(not ConvokeTheSpirits:IsKnownAndUsable() and ConvokeTheSpirits:GetTimeSinceLastCast() > 10) and
WildGrowth:GetTimeSinceLastCast() <= 6
end):SetTarget(Player)
)
DefaultAPL:AddSpell(
NaturesVigil:CastableIf(function(self)
return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and
self:IsInRange(Player) and Flourish:GetTimeSinceLastCast() <= 5
end):SetTarget(Player)
)
DefaultAPL:AddSpell(
AdaptiveSwarm:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Lowest)
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
Swiftmend:CastableIf(function(self)
return SwiftmendUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(SwiftmendUnit) and
(
SwiftmendUnit:GetHP() <= 60 or
(
Lowest:GetPartyHPAround(30, 90) >= 3 or Lowest:GetPartyHPAround(30, 85) >= 2
)
)
end):SetTarget(SwiftmendUnit)
)
DefaultAPL:AddSpell(
WildGrowth:CastableIf(function(self)
return WildGrowthUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(WildGrowthUnit) and
(
Player:GetAuras():FindMy(SoulOfTheForest):IsUp()
or
(WildGrowthUnit:GetPartyHPAround(30, 90) >= 3 or WildGrowthUnit:GetPartyHPAround(30, 85) >= 2)
) and
not Player:IsMoving()
end):SetTarget(WildGrowthUnit)
)
DefaultAPL:AddSpell(
Regrowth:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Lowest) and Lowest:GetHP() < 65 and
Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and not Player:IsMoving()
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
Regrowth:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
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
not Player:GetAuras():FindMy(SoulOfTheForest):IsUp()
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
CenarionWard:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Lowest) and Lowest:GetHP() <= 90
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
Ironbark:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Lowest) and Lowest:GetHP() <= 70 and not Lowest:GetAuras():FindMy(CenarionWard):IsUp()
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
Rejuvenation:CastableIf(function(self)
return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(RejuvUnit) and (RejuvUnit:GetHP() <= 94) and
not Player:GetAuras():FindMy(SoulOfTheForest):IsUp()
end):SetTarget(RejuvUnit)
)
DefaultAPL:AddSpell(
Lifebloom:CastableIf(function(self)
return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and
(
not Player:GetAuras():FindMy(LifebloomAura):IsUp() or
Player:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Player:IsAffectingCombat()
end):SetTarget(Player)
)
DefaultAPL:AddSpell(
Lifebloom:CastableIf(function(self)
return Tank:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and
(
not Tank:GetAuras():FindMy(LifebloomAura):IsUp() or
Tank:GetAuras():FindMy(LifebloomAura):GetRemainingTime() <= 4.5) and Tank:IsAffectingCombat()
end):SetTarget(Tank)
)
DefaultAPL:AddSpell(
Regrowth:CastableIf(function(self)
return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
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
not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and
not Player:IsMoving()
end):SetTarget(Lowest)
)
DefaultAPL:AddSpell(
Rejuvenation:CastableIf(function(self)
return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(RejuvUnit) and (RejuvUnit:GetHP() <= 94 or Player:GetPartyHPAround(40, 90) >= 2) and
not Player:GetAuras():FindMy(SoulOfTheForest):IsUp()
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:IsHostile() and
SunfireTarget:IsAffectingCombat() and Player:GetPP() >= 25
end):SetTarget(SunfireTarget)
)
DefaultAPL:AddSpell(
Moonfire:CastableIf(function(self)
return MoonfireTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(MoonfireTarget) and
(
not MoonfireTarget:GetAuras():FindMy(MoonfireAura):IsUp() or
MoonfireTarget:GetAuras():FindMy(MoonfireAura):GetRemainingTime() <= 5.4) and
MoonfireTarget:IsHostile() and
MoonfireTarget:IsAffectingCombat() and Player:GetPP() >= 25
end):SetTarget(MoonfireTarget)
)
DefaultAPL:AddSpell(
Starsurge:CastableIf(function(self)
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Target) and Target:IsHostile() and
Target:IsAffectingCombat() and Player:GetPP() >= 25
end):SetTarget(Target)
)
DefaultAPL:AddSpell(
Wrath:CastableIf(function(self)
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
and Player:CanSee(Target) and not Player:IsMoving() and
Target:IsHostile() and
Target:IsAffectingCombat() and Player:GetPP() >= 25
end):SetTarget(Target)
)
RestoModule:Sync(function()
if IsShiftKeyDown() and Player:GetAuras():FindMy(CatForm):IsUp() then
return DamageAPL:Execute()
end
DefaultAPL:Execute()
end)
Bastion:Register(RestoModule)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,24 @@
local Tinkr, Bastion = ...
local ExampleModule = Bastion.Module:New('ExampleModule')
local Player = Bastion.UnitManager:Get('player')
-- Create a local spellbook
local SpellBook = Bastion.SpellBook:New()
local FlashHeal = SpellBook:GetSpell(2061)
-- Get a global spell (this can collide with other modules, so be careful)
-- This is useful for caching common spells that you might not actually cast, and to avoid needless spell creation inline
local FlashHeal = Bastion.Globals.SpellBook:GetSpell(2061)
local AdvancedMath = Bastion:Import('AdvancedMath')
print(AdvancedMath:Add(1, 2))
ExampleModule:Sync(function()
if Player:GetHP() <= 50 then
FlashHeal:Cast(Player)
end
end)
Bastion:Register(ExampleModule)

@ -0,0 +1,21 @@
local Tinkr, Bastion = ...
local Player = Bastion.UnitManager:Get('player')
Bastion:RegisterLibrary(Bastion.Library:New({
name = 'Dependable',
exports = {
default = function()
local Dependable = {}
Dependable.__index = Dependable
function Dependable:Test(a)
print(a)
end
return Dependable
end,
Test = 5
}
}))

@ -0,0 +1,15 @@
local Tinkr, Bastion = ...
Bastion:RegisterLibrary(Bastion.Library:New({
name = 'Circular',
exports = {
default = function(self)
-- Return default first, and then the remaining exports
local Math, OtherExports = self:Import('AdvancedMath')
print(Math:Add(1, 2))
return 'Circular'
end
}
}))

@ -0,0 +1,25 @@
local Tinkr, Bastion = ...
Bastion:RegisterLibrary(Bastion.Library:New({
name = 'AdvancedMath',
exports = {
default = function(self) -- Function exports are called when the library is loaded
-- Return default first, and then the remaining exports
local Dependable, OtherExports = self:Import('Dependable')
local CircularDependency = self:Import('Circular') -- Causes a circular dependency error
Dependable:Test(OtherExports.Test)
local AdvancedMath = {}
AdvancedMath.__index = AdvancedMath
function AdvancedMath:Add(a, b)
return a + b
end
return AdvancedMath
end
}
}))

@ -1,54 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local ArcaneModule = Bastion.Module:New('arcane')
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local CDsEnabled = false
local Command = Bastion.Command:New('arcane')
Command:Register('cooldowns', 'Toggle Arcane CDs', function()
CDsEnabled = not CDsEnabled
Bastion:Print('Arcane Cooldowns ' .. (CDsEnabled and 'enabled' or 'disabled'))
end)
---@return boolean
local function CombatRotation()
return false
end
---@return boolean
local function OutOfCombatRotation()
return false
end
local isRunning = false
ArcaneModule:Sync(function()
if not isRunning then
Bastion:Print('Arcane Started')
isRunning = true
end
if not Player:IsAlive() or Player:IsMounted() then
return false
end
if Player:IsAffectingCombat() or IsCurrentSpell(AutoAttack:GetID()) then
-- Combat Rotation
return CombatRotation()
else
-- Out Of Combat Rotation
return OutOfCombatRotation()
end
end)
Bastion:Register(ArcaneModule)

@ -1,54 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local FireModule = Bastion.Module:New('fire')
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local CDsEnabled = false
local Command = Bastion.Command:New('fire')
Command:Register('cooldowns', 'Toggle Fire CDs', function()
CDsEnabled = not CDsEnabled
Bastion:Print('Fire Cooldowns ' .. (CDsEnabled and 'enabled' or 'disabled'))
end)
---@return boolean
local function CombatRotation()
return false
end
---@return boolean
local function OutOfCombatRotation()
return false
end
local isRunning = false
FireModule:Sync(function()
if not isRunning then
Bastion:Print('Fire Started')
isRunning = true
end
if not Player:IsAlive() or Player:IsMounted() then
return false
end
if Player:IsAffectingCombat() or IsCurrentSpell(AutoAttack:GetID()) then
-- Combat Rotation
return CombatRotation()
else
-- Out Of Combat Rotation
return OutOfCombatRotation()
end
end)
Bastion:Register(FireModule)

@ -1,59 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local IcyVeins = Bastion.SpellBook:GetSpell(12472)
local FrozenOrb = Bastion.SpellBook:GetSpell(84714)
local Blizzard = Bastion.SpellBook:GetSpell(190356)
Bastion.ToggleManager:Add({
id = 'Frost_CdsEnabled',
icon = IcyVeins:GetIcon(),
state = false
})
Bastion.ToggleManager:Add({
id = 'Frost_FrozenOrbEnabled',
icon = FrozenOrb:GetIcon(),
state = false
})
Bastion.ToggleManager:Add({
id = 'Frost_AutoAOE',
icon = Blizzard:GetIcon(),
state = true
})
---@alias GetFrostOption fun(option: "CdsEnabled" | "FrozenOrbEnabled" | 'AutoAOE'): boolean
---@type GetFrostOption
local function GetFrostOption(option)
return Bastion.ToggleManager:GetState("Frost_" .. option)
end
local Command = Bastion.Command:New('frost')
Command:Register('aoe', 'Toggle bastion on/off', function()
Bastion.ToggleManager:Toggle("Frost_AutoAOE")
end)
Command:Register('cds', 'Toggle bastion on/off', function()
Bastion.ToggleManager:Toggle("Frost_CdsEnabled")
if GetFrostOption("FrozenOrbEnabled") and not GetFrostOption("CdsEnabled") then
Bastion.ToggleManager:Toggle("Frost_FrozenOrbEnabled")
elseif not GetFrostOption("FrozenOrbEnabled") and GetFrostOption("CdsEnabled") then
Bastion.ToggleManager:Toggle("Frost_FrozenOrbEnabled")
end
end)
Command:Register('orb', 'Toggle bastion on/off', function()
Bastion.ToggleManager:Toggle("Frost_FrozenOrbEnabled")
if not GetFrostOption("FrozenOrbEnabled") and GetFrostOption("CdsEnabled") then
Bastion.ToggleManager:Toggle("Frost_CdsEnabled")
end
end)
return GetFrostOption

@ -1,206 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local FrostModule = Bastion.Module:New('frost')
local Player = Bastion.UnitManager:Get('player')
local Target = Bastion.UnitManager:Get('target')
local None = Bastion.UnitManager:Get('none')
local AutoAttack = Bastion.SpellBook:GetSpell(6603)
local Frostbolt = Bastion.SpellBook:GetSpell(116)
local IceLance = Bastion.SpellBook:GetSpell(30455)
local Flurry = Bastion.SpellBook:GetSpell(44614)
local BrainFreeze = Bastion.SpellBook:GetSpell(190446)
local FingersOfFrost = Bastion.SpellBook:GetSpell(44544)
local WintersChill = Bastion.SpellBook:GetSpell(228358)
local FrostNova = Bastion.SpellBook:GetSpell(122)
local IceNova = Bastion.SpellBook:GetSpell(157997)
local IcyVeins = Bastion.SpellBook:GetSpell(12472)
local FrozenOrb = Bastion.SpellBook:GetSpell(84714)
local Blizzard = Bastion.SpellBook:GetSpell(190356)
local RuneOfPower = Bastion.SpellBook:GetSpell(116011)
---@type GetRangedTargetCount
local GetRangedTargetCount = Tinkr:require("scripts/bastion/scripts/shared/get-ranged-target-count", Bastion)
local EnableDrawROP = Tinkr:require("scripts/bastion/scripts/shared/draw-rop", Bastion)
---@type GetFrostOption
local GetFrostOption = Tinkr:require("scripts/bastion/scripts/frost/frost-gui", Bastion)
---@param unit Unit
local function isUnitFrozen(unit)
if not unit:Exists() then return false end
if unit:GetAuras():FindMy(IceNova):IsUp() or unit:GetAuras():FindMy(FrostNova):IsUp() then
return true
end
return false
end
local IceLanceTarget = Bastion.UnitManager:CreateCustomUnit('icelance', function()
if not IceLance:IsKnownAndUsable() then return None end
local icelance_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 IceLance:IsInRange(unit) then
return false
end
if not Player:IsFacing(unit) then
return false
end
if IceLance:IsInFlightToUnit(unit) then
return false
end
if isUnitFrozen(unit) then
icelance_target = unit
return true
end
return false
end)
if icelance_target == nil then
icelance_target = None
end
return icelance_target
end)
local FrozenOrbTarget = Bastion.UnitManager:CreateCustomUnit('frozen_orb', function()
local unit = Bastion.UnitManager:GetEnemyWithMostEnemies(40, 10)
if not unit then
if Target:Exists() and Target:GetDistance(Player) <= 40 then
unit = Target
else
unit = None
end
end
return unit
end)
local HasRotatedForOrb = false
FrozenOrb:OnCast(function()
HasRotatedForOrb = false
end)
---@return boolean
local function CombatRotation()
local targetCount = GetRangedTargetCount(40)
if GetFrostOption("CdsEnabled") and IcyVeins:IsKnownAndUsable() and not Player:IsMoving() and Target:Exists() then
return IcyVeins:Cast(Player)
end
if GetFrostOption("CdsEnabled") and RuneOfPower:IsKnownAndUsable() and not Player:GetAuras():FindMy(RuneOfPower):IsUp() and not Player:IsMoving() and Target:Exists() and IcyVeins:GetLastCastTime() ~= false and GetTime() - IcyVeins:GetLastCastTime() > 5 then
return RuneOfPower:Cast(Player)
end
if FrozenOrb:IsKnownAndUsable() and GetFrostOption("FrozenOrbEnabled") and FrozenOrbTarget:Exists() then
if not HasRotatedForOrb then
print("TURNING TO ORB TARGET")
FaceObject(FrozenOrbTarget:GetGUID())
SendMovementHeartbeat()
HasRotatedForOrb = true
else
return FrozenOrb:Cast(Player)
end
end
if IceLanceTarget:Exists() then
return IceLance:Cast(IceLanceTarget)
end
if Target:Exists() and Player:CanSee(Target) and IceLance:IsKnownAndUsable() and IceLance:IsInRange(Target) then
-- Winters Chill
if Target:GetAuras():FindMy(WintersChill):IsUp() then
local winters_chill_stacks = Target:GetAuras():FindMy(WintersChill):GetCount()
print("WC STACKS: ", winters_chill_stacks)
if winters_chill_stacks == 1 and not IceLance:IsInFlightToUnit(Target) then
return IceLance:Cast(Target)
end
if winters_chill_stacks > 1 then
return IceLance:Cast(Target)
end
end
-- Fingers of Frost
if Player:GetAuras():FindMy(FingersOfFrost):IsUp() then
local fof_stacks = Player:GetAuras():FindMy(FingersOfFrost):GetCount()
print("FOF STACKS: ", fof_stacks)
if fof_stacks == 1 and not IceLance:IsInFlightToUnit(Target) then
return IceLance:Cast(Target)
end
if fof_stacks > 1 then
return IceLance:Cast(Target)
end
end
end
if Target:Exists() and Player:CanSee(Target) and Flurry:IsKnownAndUsable() and Player:GetAuras():FindMy(BrainFreeze):IsUp() then
local CastingSpell = Player:GetCastingOrChannelingSpell()
if Player:IsMoving() or (CastingSpell ~= nil and CastingSpell:GetID() == Frostbolt:GetID()) then
return Flurry:Cast(Target)
end
end
if Target:Exists() and Player:CanSee(Target) and not Player:IsMoving() and Frostbolt:IsKnownAndUsable() and Frostbolt:IsInRange(Target) then
return Frostbolt:Cast(Target)
end
return false
end
---@return boolean
local function OutOfCombatRotation()
return false
end
local isRunning = false
FrostModule:Sync(function()
if not isRunning then
Bastion:Print('Frost Started')
isRunning = true
end
EnableDrawROP()
if not Player:IsAlive() or Player:IsMounted() then
return false
end
if Player:IsAffectingCombat() or IsCurrentSpell(AutoAttack:GetID()) then
-- Combat Rotation
return CombatRotation()
else
-- Out Of Combat Rotation
return OutOfCombatRotation()
end
end)
Bastion:Register(FrostModule)

@ -1,45 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local Player = Bastion.UnitManager:Get('player')
local RuneOfPower = Bastion.SpellBook:GetSpell(116011)
local Draw = Tinkr.Util.Draw:New()
Draw:Sync(function(draw)
for i, object in ipairs(Objects()) do
local name = ObjectName(object)
if name == RuneOfPower:GetName() then
local creator = ObjectCreator(object)
if tostring(creator) == tostring(Player:GetGUID()) then
local distance = ObjectDistance('player', object)
local ropX, ropY, ropZ = ObjectPosition(object)
draw:Circle(ropX, ropY, ropZ, 8)
draw:SetColor(69, 7, 58)
if distance > 8 then
draw:SetColor(252, 3, 207)
end
end
end
end
end)
local isEnabled = false
-- Draws a circle around the players Rune of Power to make it easier to stay within range.
local function EnableDrawROP()
if not isEnabled then
isEnabled = true
Draw:Enable()
end
end
return EnableDrawROP

@ -1,25 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
local Player = Bastion.UnitManager:Get('player')
---@alias GetRangedTargetCount fun(range: number): number
---@type GetRangedTargetCount
local function GetRangedTargetCount(range)
local count = 0
Bastion.UnitManager:EnumEnemies(function(unit)
if not unit:IsDead() and unit:IsAffectingCombat() and Player:CanSee(unit) and Player:GetDistance(unit) <= range and Player:IsFacing(unit) then
count = count + 1
return false
end
return false
end)
return count
end
return GetRangedTargetCount

@ -1,5 +1,4 @@
-- Document with emmy lua: https://emmylua.github.io/ -- Document with emmy lua: https://emmylua.github.io/
-- Create an APL trait for the APL class -- Create an APL trait for the APL class
---@class APLTrait ---@class APLTrait
local APLTrait = {} local APLTrait = {}
@ -55,7 +54,7 @@ end
---@param ... APLTrait ---@param ... APLTrait
---@return APLActor ---@return APLActor
function APLActor:AddTraits(...) function APLActor:AddTraits(...)
for _, trait in ipairs({ ... }) do for _, trait in ipairs({...}) do
table.insert(self.traits, trait) table.insert(self.traits, trait)
end end
@ -85,13 +84,11 @@ 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 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().sequencer then
if self:GetActor().condition and self:GetActor().condition() and not self:GetActor().sequencer:Finished() then if self:GetActor().condition and self:GetActor().condition() and not self:GetActor().sequencer:Finished() then
print("Execute?")
self:GetActor().sequencer:Execute() self:GetActor().sequencer:Execute()
return true return true
end end
if not self:GetActor().condition and not self:GetActor().sequencer:Finished() then if not self:GetActor().condition and not self:GetActor().sequencer:Finished() then
print("Execute?")
self:GetActor().sequencer:Execute() self:GetActor().sequencer:Execute()
return true return true
end end
@ -110,23 +107,24 @@ function APLActor:Execute()
if self:GetActor().spell then if self:GetActor().spell then
if self:GetActor().condition then if self:GetActor().condition then
-- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName()) -- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().spell:CastableIf(self:GetActor().castableFunc):Cast(self:GetActor().target, self:GetActor().spell:CastableIf(self:GetActor().castableFunc):OnCast(self:GetActor().onCastFunc):Cast(
self:GetActor().condition) self:GetActor().target, self:GetActor().condition)
end else
-- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName()) -- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().spell:CastableIf(self:GetActor().castableFunc):Cast(self:GetActor().target) self:GetActor().spell:CastableIf(self:GetActor().castableFunc):OnCast(self:GetActor().onCastFunc):Cast(
self:GetActor().target)
end
end end
if self:GetActor().item then if self:GetActor().item then
if self:GetActor().condition then if self:GetActor().condition then
-- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName()) -- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target, self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target,
self:GetActor().condition) self:GetActor().condition)
end else
-- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName()) -- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target) self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target)
end end
end
if self:GetActor().action then if self:GetActor().action then
-- print("Bastion: APL:Execute: Executing action " .. self:GetActor().action) -- print("Bastion: APL:Execute: Executing action " .. self:GetActor().action)
self:GetActor().cb(self) self:GetActor().cb(self)
@ -187,7 +185,11 @@ end
---@param cb fun(...):any ---@param cb fun(...):any
---@return APLActor ---@return APLActor
function APL:AddVariable(name, cb) function APL:AddVariable(name, cb)
local actor = APLActor:New({ variable = name, cb = cb, _apl = self }) local actor = APLActor:New({
variable = name,
cb = cb,
_apl = self
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
return actor return actor
end end
@ -197,20 +199,30 @@ end
---@param cb fun(...):any ---@param cb fun(...):any
---@return APLActor ---@return APLActor
function APL:AddAction(action, cb) function APL:AddAction(action, cb)
local actor = APLActor:New({ action = action, cb = cb }) local actor = APLActor:New({
action = action,
cb = cb
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
return actor return actor
end end
-- Add a spell to the APL -- Add a spell to the APL
---@param spell Spell ---@param spell Spell
---@param condition fun(...):boolean ---@param condition? string|fun(...):boolean
---@return APLActor ---@return APLActor
function APL:AddSpell(spell, condition) function APL:AddSpell(spell, condition)
local castableFunc = spell.CastableIfFunc local castableFunc = spell.CastableIfFunc
local onCastFunc = spell.OnCastFunc
local target = spell:GetTarget() local target = spell:GetTarget()
local actor = APLActor:New({ spell = spell, condition = condition, castableFunc = castableFunc, target = target }) local actor = APLActor:New({
spell = spell,
condition = condition,
castableFunc = castableFunc,
target = target,
onCastFunc = onCastFunc
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
@ -219,13 +231,18 @@ end
-- Add an item to the APL -- Add an item to the APL
---@param item Item ---@param item Item
---@param condition fun(...):boolean ---@param condition? fun(...):boolean
---@return APLActor ---@return APLActor
function APL:AddItem(item, condition) function APL:AddItem(item, condition)
local usableFunc = item.UsableIfFunc local usableFunc = item.UsableIfFunc
local target = item:GetTarget() local target = item:GetTarget()
local actor = APLActor:New({ item = item, condition = condition, usableFunc = usableFunc, target = target }) local actor = APLActor:New({
item = item,
condition = condition,
usableFunc = usableFunc,
target = target
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
@ -240,7 +257,10 @@ function APL:AddAPL(apl, condition)
if not condition then if not condition then
error("Bastion: APL:AddAPL: No condition for APL " .. apl.name) error("Bastion: APL:AddAPL: No condition for APL " .. apl.name)
end end
local actor = APLActor:New({ apl = apl, condition = condition }) local actor = APLActor:New({
apl = apl,
condition = condition
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
return actor return actor
end end
@ -248,14 +268,12 @@ end
-- Execute the APL -- Execute the APL
function APL:Execute() function APL:Execute()
for _, actor in ipairs(self.apl) do for _, actor in ipairs(self.apl) do
if actor:HasTraits() and actor:Evaluate() then if actor:HasTraits() then
if actor:Execute() then if actor:Evaluate() and actor:Execute() then
print("BREAQK", actor)
break break
end end
else else
if actor:Execute() then if actor:Execute() then
print("BREAQK", actor)
break break
end end
end end
@ -267,7 +285,10 @@ end
---@param condition fun(...):boolean ---@param condition fun(...):boolean
---@return APLActor ---@return APLActor
function APL:AddSequence(sequencer, condition) function APL:AddSequence(sequencer, condition)
local actor = APLActor:New({ sequencer = sequencer, condition = condition }) local actor = APLActor:New({
sequencer = sequencer,
condition = condition
})
table.insert(self.apl, actor) table.insert(self.apl, actor)
return actor return actor
end end

@ -1,5 +1,4 @@
-- Document with emmy lua: https://emmylua.github.io/ -- Document with emmy lua: https://emmylua.github.io/
local Tinkr, Bastion = ... local Tinkr, Bastion = ...
-- Create a new Aura class -- Create a new Aura class
@ -66,19 +65,52 @@ function Aura:New(unit, index, type)
timeMod = 0, timeMod = 0,
index = nil, index = nil,
type = nil, type = nil
} }
if self.aura.spellId then if self.aura.spellId then
Bastion.SpellBook:GetSpell(self.aura.spellId) Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
end end
return self return self
end end
local name, icon, count, dispelType, duration, expirationTime, source, isStealable, nameplateShowPersonal, if C_UnitAuras.GetAuraDataByIndex then
spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = UnitAura(unit:GetOMToken(), index, local unitAuraInfo = C_UnitAuras.GetAuraDataByIndex(unit:GetOMToken(), index, type)
type) if not unitAuraInfo then
local self = setmetatable({}, Aura)
self.aura = {
name = nil,
icon = nil,
count = 0,
dispelType = nil,
duration = 0,
expirationTime = 0,
source = nil,
isStealable = false,
nameplateShowPersonal = false,
spellId = 0,
canApplyAura = false,
isBossDebuff = false,
castByPlayer = false,
nameplateShowAll = false,
timeMod = 0,
index = nil,
type = nil
}
if self.aura.spellId then
Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
end
return self
end
return Aura:CreateFromUnitAuraInfo(unitAuraInfo)
end
local name, icon, count, dispelType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId,
canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = UnitAura(unit:GetOMToken(), index, type)
local self = setmetatable({}, Aura) local self = setmetatable({}, Aura)
self.aura = { self.aura = {
@ -100,10 +132,10 @@ function Aura:New(unit, index, type)
auraInstanceID = nil, auraInstanceID = nil,
index = index, index = index,
type = type, type = type
} }
if self.aura.spellId then if self.aura.spellId then
Bastion.SpellBook:GetSpell(self.aura.spellId) Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
end end
return self return self
end end
@ -132,11 +164,11 @@ function Aura:CreateFromUnitAuraInfo(unitAuraInfo)
auraInstanceID = unitAuraInfo.auraInstanceID, auraInstanceID = unitAuraInfo.auraInstanceID,
index = nil, index = nil,
type = unitAuraInfo.isHarmful and "HARMFUL" or "HELPFUL", type = unitAuraInfo.isHarmful and "HARMFUL" or "HELPFUL"
} }
-- Register spell in spellbook -- Register spell in spellbook
Bastion.SpellBook:GetSpell(self.aura.spellId) Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
return self return self
end end
@ -239,7 +271,7 @@ end
-- Get the auras spell id -- Get the auras spell id
---@return Spell ---@return Spell
function Aura:GetSpell() function Aura:GetSpell()
return Bastion.SpellBook:GetSpell(self.aura.spellId) return Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
end end
-- Get the auras can apply aura status -- Get the auras can apply aura status

@ -53,7 +53,7 @@ function AuraTable:OnUpdate(auras)
if updatedAuras and #updatedAuras > 0 then if updatedAuras and #updatedAuras > 0 then
for i = 1, #updatedAuras do for i = 1, #updatedAuras do
local id = updatedAuras[i] local id = updatedAuras[i]
local newAura = C_UnitAuras_GetAuraDataByAuraInstanceID(self.unit:GetOMToken(), id); local newAura = C_UnitAuras.GetAuraDataByAuraInstanceID(self.unit:GetOMToken(), id);
if newAura then if newAura then
local aura = Bastion.Aura:CreateFromUnitAuraInfo(newAura) local aura = Bastion.Aura:CreateFromUnitAuraInfo(newAura)
self:AddOrUpdateAuraInstanceID(aura:GetAuraInstanceID(), aura) self:AddOrUpdateAuraInstanceID(aura:GetAuraInstanceID(), aura)
@ -118,7 +118,7 @@ end
-- Get a units buffs -- Get a units buffs
---@return nil ---@return nil
function AuraTable:GetUnitBuffs() function AuraTable:GetUnitBuffs()
if Tinkr.classic then if Tinkr.classic or Tinkr.era then
for i = 1, 40 do for i = 1, 40 do
local aura = Bastion.Aura:New(self.unit, i, 'HELPFUL') local aura = Bastion.Aura:New(self.unit, i, 'HELPFUL')
@ -157,7 +157,7 @@ end
-- Get a units debuffs -- Get a units debuffs
---@return nil ---@return nil
function AuraTable:GetUnitDebuffs() function AuraTable:GetUnitDebuffs()
if Tinkr.classic then if Tinkr.classic or Tinkr.era then
for i = 1, 40 do for i = 1, 40 do
local aura = Bastion.Aura:New(self.unit, i, 'HARMFUL') local aura = Bastion.Aura:New(self.unit, i, 'HARMFUL')
@ -275,12 +275,11 @@ function AuraTable:Find(spell)
end end
for k, a in pairs(aurasub) do for k, a in pairs(aurasub) do
print(a)
if a ~= nil then if a ~= nil then
if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA
return a return a
else else
if not Tinkr.classic then if not Tinkr.classic or Tinkr.era then
self:RemoveInstanceID(a:GetAuraInstanceID()) self:RemoveInstanceID(a:GetAuraInstanceID())
end end
end end
@ -306,7 +305,7 @@ function AuraTable:FindMy(spell)
if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA
return a return a
else else
if not Tinkr.classic then if not Tinkr.classic or Tinkr.era then
self:RemoveInstanceID(a:GetAuraInstanceID()) self:RemoveInstanceID(a:GetAuraInstanceID())
end end
end end
@ -335,7 +334,7 @@ function AuraTable:FindFrom(spell, source)
return a return a
end end
else else
if not Tinkr.classic then if not Tinkr.classic or Tinkr.era then
self:RemoveInstanceID(a:GetAuraInstanceID()) self:RemoveInstanceID(a:GetAuraInstanceID())
end end
end end
@ -363,7 +362,7 @@ function AuraTable:FindTheirs(spell)
return a return a
end end
else else
if not Tinkr.classic then if not Tinkr.classic or Tinkr.era then
self:RemoveInstanceID(a:GetAuraInstanceID()) self:RemoveInstanceID(a:GetAuraInstanceID())
end end
end end
@ -385,6 +384,351 @@ function AuraTable:FindAny(spell)
return self:FindMy(spell) return self:FindMy(spell)
end end
-- FindAnyOf
---@param spells List
---@return Aura
function AuraTable:FindAnyOf(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindAny(cur)
if aura:IsValid() then
return aura, true
end
return acc
end, Bastion.Aura:New())
end
-- FindAnyOfMy
---@param spells List
---@return Aura
function AuraTable:FindAnyOfMy(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindMy(cur)
if aura:IsValid() then
return aura, true
end
return acc
end, Bastion.Aura:New())
end
-- FindAnyOfTheirs
---@param spells List
---@return Aura
function AuraTable:FindAnyOfTheirs(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindTheirs(cur)
if aura:IsValid() then
return aura, true
end
return acc
end, Bastion.Aura:New())
end
-- FindAnyFrom
---@param spells List
---@param source Unit
---@return Aura
function AuraTable:FindAnyFrom(spells, source)
return spells:reduce(function(acc, cur)
local aura = self:FindFrom(cur, source)
if aura:IsValid() then
return aura, true
end
return acc
end, Bastion.Aura:New())
end
-- FindLongestOf
---@param spells List
---@return Aura
function AuraTable:FindLongestOf(spells)
return spells:reduce(function(acc, cur)
local aura = self:Find(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() > acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLongestOfMy
---@param spells List
---@return Aura
function AuraTable:FindLongestOfMy(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindMy(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() > acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLongestOfTheirs
---@param spells List
---@return Aura
function AuraTable:FindLongestOfTheirs(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindTheirs(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() > acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLongestOfFrom
---@param spells List
---@param source Unit
---@return Aura
function AuraTable:FindLongestOfFrom(spells, source)
return spells:reduce(function(acc, cur)
local aura = self:FindFrom(cur, source)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() > acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindShortestOf
---@param spells List
---@return Aura
function AuraTable:FindShortestOf(spells)
return spells:reduce(function(acc, cur)
local aura = self:Find(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() < acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindShortestOfMy
---@param spells List
---@return Aura
function AuraTable:FindShortestOfMy(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindMy(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() < acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindShortestOfTheirs
---@param spells List
---@return Aura
function AuraTable:FindShortestOfTheirs(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindTheirs(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() < acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindShortestOfFrom
---@param spells List
---@param source Unit
---@return Aura
function AuraTable:FindShortestOfFrom(spells, source)
return spells:reduce(function(acc, cur)
local aura = self:FindFrom(cur, source)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetRemainingTime() < acc:GetRemainingTime() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindMostOf
---@param spells List
---@return Aura
function AuraTable:FindMostOf(spells)
return spells:reduce(function(acc, cur)
local aura = self:Find(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() > acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindMostOfMy
---@param spells List
---@return Aura
function AuraTable:FindMostOfMy(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindMy(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() > acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindMostOfTheirs
---@param spells List
---@return Aura
function AuraTable:FindMostOfTheirs(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindTheirs(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() > acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindMostOfFrom
---@param spells List
---@param source Unit
---@return Aura
function AuraTable:FindMostOfFrom(spells, source)
return spells:reduce(function(acc, cur)
local aura = self:FindFrom(cur, source)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() > acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLeastOf
---@param spells List
---@return Aura
function AuraTable:FindLeastOf(spells)
return spells:reduce(function(acc, cur)
local aura = self:Find(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() < acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLeastOfMy
---@param spells List
---@return Aura
function AuraTable:FindLeastOfMy(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindMy(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() < acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLeastOfTheirs
---@param spells List
---@return Aura
function AuraTable:FindLeastOfTheirs(spells)
return spells:reduce(function(acc, cur)
local aura = self:FindTheirs(cur)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() < acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- FindLeastOfFrom
---@param spells List
---@param source Unit
---@return Aura
function AuraTable:FindLeastOfFrom(spells, source)
return spells:reduce(function(acc, cur)
local aura = self:FindFrom(cur, source)
if aura:IsValid() then
if not acc:IsValid() then
return aura
end
if aura:GetCount() < acc:GetCount() then
return aura
end
end
return acc
end, Bastion.Aura:New())
end
-- Has any stealable aura -- Has any stealable aura
---@return boolean ---@return boolean
function AuraTable:HasAnyStealableAura() function AuraTable:HasAnyStealableAura()
@ -422,21 +766,4 @@ function AuraTable:HasAnyDispelableAura(spell)
return false return false
end end
-- Loop over a units auras
---@param cb fun(spellId: number): boolean | nil
---@return boolean
function AuraTable:ForEach(cb)
for _, auras in pairs(self:GetUnitAuras()) do
for _, aura in pairs(auras) do
if aura:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA
cb(aura.spellId)
else
self:RemoveInstanceID(aura:GetAuraInstanceID())
end
end
end
return false
end
return AuraTable return AuraTable

@ -18,12 +18,21 @@ function Class:__index(k)
return response return response
end end
---@class Class
---@field class Class.class
---@class Class.class
---@field locale string
---@field name string
---@field id number
-- Constructor -- Constructor
---@param locale string ---@param locale string
---@param name string ---@param name string
---@param id number ---@param id number
function Class:New(locale, name, id) function Class:New(locale, name, id)
local self = setmetatable({}, Class) local self = setmetatable({}, Class)
self.class = { self.class = {
locale = locale, locale = locale,
name = name, name = name,
@ -50,10 +59,16 @@ function Class:GetID()
return self.class.id return self.class.id
end end
---@class ColorMixin
---@field r number
---@field g number
---@field b number
-- Return the classes color -- Return the classes color
---@return ColorMixin classColor ---@return ColorMixin classColor
function Class:GetColor() function Class:GetColor()
return C_ClassColor.GetClassColor(self.class.name) return C_ClassColor.GetClassColor(self.class.name)
end end
return Class return Class

@ -1,6 +1,13 @@
-- Create a wow command handler class -- Create a wow command handler class
---@class Command ---@class Command
---@field command string
---@field commands Command.commands[]
local Command = {} local Command = {}
---@class Command.commands
---@field helpmsg string
---@field cb fun(args: table)
Command.__index = Command Command.__index = Command
---@return string ---@return string

@ -1,201 +0,0 @@
local Tinkr, _Bastion = ...
---@class Bastion
local Bastion = _Bastion
---@alias BigWigsBars table<string, { spellId: number, duration: number, startTime: number, pauseTime: number | nil }>
---@class ContentUtils
local ContentUtils = {
---@type InterruptList
interruptList = {},
---@type TankBusterList
tankBusterList = {},
---@type ImportantAuraList
importantAuras = {},
---@type BigWigsBars
bars = {},
isBigWigsEnabled = false
}
ContentUtils.__index = ContentUtils
local function BWEventCallBack(event, ...)
if event == "BigWigs_StartBar" then
local addon, spellId, text, duration, icon, isCD = ...
local now = GetTime()
print('START BAR', text)
ContentUtils.bars[text] = ContentUtils.bars[text] or {}
ContentUtils.bars[text].duration = duration
ContentUtils.bars[text].startTime = now
ContentUtils.bars[text].spellId = spellId
elseif event == "BigWigs_StopBar" then
local addon, text = ...
print('STOP BAR', text)
if ContentUtils.bars[text] then
ContentUtils.bars[text] = nil
end
elseif event == "BigWigs_PauseBar" then
local addon, text = ...
print('PAUSE BAR', text)
if ContentUtils.bars[text] then
ContentUtils.bars[text].pauseTime = GetTime()
end
elseif event == "BigWigs_ResumeBar" then
local addon, text = ...
print('RESUME BAR', text)
if ContentUtils.bars[text] and ContentUtils.bars[text].pauseTime then
local pauseTime = ContentUtils.bars[text].pauseTime
local startTime = ContentUtils.bars[text].startTime
local duration = ContentUtils.bars[text].duration
local newDuration = duration - (pauseTime - startTime)
ContentUtils.bars[text].pauseTime = nil
ContentUtils.bars[text].duration = newDuration
ContentUtils.bars[text].startTime = GetTime()
end
elseif event == "BigWigs_StopBars" or event == "BigWigs_OnBossDisable" then
ContentUtils.bars = {}
end
end
---@type InterruptList
local _interruptList = Tinkr:require("scripts/bastion/ContentUtils/lists/interrupt-list", Bastion)
---@type TankBusterList
local _tankBusterList = Tinkr:require("scripts/bastion/ContentUtils/lists/tank-buster-list", Bastion)
---@type ImportantAuraList
local _importantAuraList = Tinkr:require("scripts/bastion/ContentUtils/lists/important-aura-list", Bastion)
---@return ContentUtils
function ContentUtils:New()
local self = setmetatable({}, ContentUtils)
self.interruptList = _interruptList
self.tankBusterList = _tankBusterList
self.importantAuras = _importantAuraList
if BigWigsLoader then
print("BigWigs Enabled!")
self.isBigWigsEnabled = true
BigWigsLoader.RegisterMessage({}, 'BigWigs_StartBar', BWEventCallBack)
BigWigsLoader.RegisterMessage({}, 'BigWigs_StopBar', BWEventCallBack)
BigWigsLoader.RegisterMessage({}, 'BigWigs_PauseBar', BWEventCallBack)
BigWigsLoader.RegisterMessage({}, 'BigWigs_ResumeBar', BWEventCallBack)
BigWigsLoader.RegisterMessage({}, 'BigWigs_StopBars', BWEventCallBack)
BigWigsLoader.RegisterMessage({}, 'BigWigs_OnBossDisable', BWEventCallBack)
end
return self
end
---@param unit Unit
---@param percent number
---@return Interrupt | nil
function ContentUtils:CastingCriticalStop(unit, percent)
local castingSpell = unit:GetCastingOrChannelingSpell()
local npcSpells = self.interruptList[unit:GetID()]
if npcSpells and castingSpell then
local spellID = castingSpell:GetID()
local spellTraits = npcSpells[spellID]
if not spellTraits then
return nil
end
local castPercent = unit:GetChannelOrCastPercentComplete()
if castPercent >= percent then
return spellTraits
end
end
return nil
end
function ContentUtils:RefreshBars()
if self.isBigWigsEnabled then
for key, value in pairs(self.bars) do
local expireTime = value.duration + value.startTime
if expireTime < GetTime() then
self.bars[key] = nil
end
end
end
end
---@param spellId number
---@param timeUntilCast number
---@return boolean
function ContentUtils:IsSpellCastSoon(spellId, timeUntilCast)
if self.isBigWigsEnabled then
self:RefreshBars()
for key, value in pairs(self.bars) do
if value.spellId == spellId and not value.pauseTime then
local expireTime = value.duration + value.startTime
local timeUntilSpell = expireTime - GetTime()
if timeUntilSpell > 0 and timeUntilSpell <= timeUntilCast then
return true
end
end
end
end
return false
end
---@param unit Unit
---@param timeUntilCast? number
---@return TankBuster | nil
function ContentUtils:CastingTankBuster(unit, timeUntilCast)
local npcSpells = self.tankBusterList[unit:GetID()]
if npcSpells then
-- If timeUntilCast is provided, check if any of this NPC
if timeUntilCast then
for key, value in pairs(self.bars) do
local spellTraits = npcSpells[value.spellId]
if spellTraits and not value.pauseTime and ((value.startTime + value.duration) - GetTime()) <= timeUntilCast then
return spellTraits
end
end
end
local castingSpell = unit:GetCastingOrChannelingSpell()
if castingSpell then
if npcSpells[castingSpell:GetID()] then return npcSpells[castingSpell:GetID()] end
end
end
return nil
end
---@param friend Unit
---@return ImportantAura | nil
function ContentUtils:HasImportantAura(friend)
local traits = nil
friend:GetAuras():ForEach(function(spellId)
local auraTraits = self.importantAuras[spellId]
if auraTraits then traits = auraTraits end
end)
return traits
end
return ContentUtils

@ -1,193 +0,0 @@
---@alias ImportantAura { isMagicDispel: boolean | nil, isPoisonDispel: boolean | nil, isDiseaseDispel: boolean | nil, isDiffusable: boolean | nil }
---@alias ImportantAuraList table<number, ImportantAura>
---@type ImportantAuraList
local _ImportantAuraList = {
-- Primal Chill
[372682] = {
isMagicDispel = true
},
-- Primal Chill
[373589] = {
isMagicDispel = true
},
-- Rolling Thunder
[392641] = {
isMagicDispel = true
},
-- Burning Touch
[373869] = {
isMagicDispel = true,
isDiffusable = true
},
-- Shock Blast
[392924] = {
isMagicDispel = true,
isDiffusable = true
},
-- Storm Slam
[381515] = {
isMagicDispel = true
},
-- Thunder Clap
[386028] = {
isMagicDispel = true
},
-- Tempest
[386025] = {
isMagicDispel = true,
isDiffusable = true
},
-- Frightful Roar
[386063] = {
isMagicDispel = true
},
-- Bloodcurdling Shout
[373395] = {
isMagicDispel = true
},
-- Conductive Strike
[376827] = {
isMagicDispel = true
},
-- Storm Shock
[381530] = {
isMagicDispel = true
},
-- Mystic Vapors
[387564] = {
isMagicDispel = true,
isDiffusable = true
},
-- Erratic Growth
[375602] = {
isMagicDispel = true
},
-- Crystaline Rupture
[370766] = {
isMagicDispel = true
},
-- Waking Bane
[386546] = {
isMagicDispel = true
},
-- Waking Bane
[386549] = {
isMagicDispel = true
},
-- Forbiden Knowledge
[371352] = {
isMagicDispel = true
},
-- Icy Bindings
[377488] = {
isMagicDispel = true
},
-- Absolute Zero
[396722] = {
isMagicDispel = true
},
-- Dragon Strike
[384978] = {
isMagicDispel = true,
isDiffusable = true
},
-- Monotonous Lecture
[388392] = {
isMagicDispel = true
},
-- Thunderstrike
[215429] = {
isMagicDispel = true
},
-- Seal Magic
[209404] = {
isMagicDispel = true
},
-- Mana Fang
[209516] = {
isMagicDispel = true,
isDiffusable = true
},
-- Suppress
[209413] = {
isMagicDispel = true,
},
-- Bewitch
[211470] = {
isMagicDispel = true,
isDiffusable = true
},
-- Impending Doom
[397907] = {
isMagicDispel = true
},
-- Withering Soul
[208165] = {
isMagicDispel = true
},
-- Criple
[214690] = {
isMagicDispel = true
},
-- Carrion Swarm
[214688] = {
isMagicDispel = true
},
-- Shadow Word: Frailty
[152819] = {
isMagicDispel = true,
isDiffusable = true
},
-- Tainted Ripple
[397878] = {
isMagicDispel = true
},
-- Haunting Scream
[395859] = {
isMagicDispel = true,
},
-- Throw Torch
[114803] = {
isMagicDispel = true,
isDiffusable = true
},
-- Sleepy Soliloquy
[395872] = {
isMagicDispel = true,
},
-- Serpent Strike
[106823] = {
isMagicDispel = true,
},
-- Necrotic Burst
[156718] = {
isDiseaseDispel = true
},
-- Plague Spit
[153524] = {
isDiseaseDispel = true
},
-- Death Venom
[156717] = {
isPoisonDispel = true
},
-- Lasher Toxin
[389033] = {
isPoisonDispel = true
},
-- Touch of Nothingness
[106113] = {
isMagicDispel = true,
},
-- Arcane Lockdown
[207278] = {
isDiffusable = true
},
-- Hinder
[215204] = {
isDiffusable = true
}
}
return _ImportantAuraList

@ -1,662 +0,0 @@
---@alias Interrupt { kickable: boolean, sweepable: boolean, paraliseable: boolean }
---@alias InterruptList table<number, table<number, Interrupt>>
---@type InterruptList
local _InterruptList = {
-- kick, stun, disorient, incap
--
-- HoV
--
-- Thunder Caller
[95842] = {
-- Thunderous Blast
[198585] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Mystic
[95834] = {
-- Holy Radiance
[215433] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Runcarver
[96664] = {
-- Etch
[198959] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Marksman
[96640] = {
-- Penetrating Shot
[199210] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Shieldmaiden
[95832] = {
-- Mortal Hew
[199050] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Shieldmaiden 2
[101639] = {
-- Mortal Hew
[199050] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Odyn Add
[102019] = {
-- Surge
[198750] = {
kickable = true, sweepable = false, paraliseable = false
}
},
--
-- Court of Stars
--
-- Duskwatch Guard
[111563] = {
-- Fortification
[209033] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Duskwatch Guard 2
[104246] = {
-- Fortification
[209033] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Sentry
[104251] = {
-- Sound the Alarm
[210261] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Construct
[104270] = {
-- Suppress
[209413] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Charging Station
[225100] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Bound Energy
[105705] = {
-- Charged Blast
[212031] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Arcane Manifestation
[105704] = {
-- Drain Magic
[209485] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Watchful Inquisitor
[105715] = {
-- Searing Glare
[211299] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Eye Storm
[212784] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Blazing Imp
[104295] = {
-- Drifting Embers
[211401] = {
kickable = true, sweepable = true, paraliseable = false
},
-- Drifting Embers 2
[211406] = {
kickable = true, sweepable = true, paraliseable = false
},
-- Fireball
[211412] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Shadow Mistress
[104300] = {
-- Bewitch
[211470] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Balgarr
[104274] = {
-- Disintegration Beam
[207980] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Disintigration Beam 2
[207981] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Impending Doom
[397907] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Impending Doom 2
[397908] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Talixae Flamewreath
[104217] = {
-- Withering Soul
[208165] = {
kickable = true, sweepable = false, paraliseable = false
}
},
--
-- RLP
--
-- Earthshaper
[187969] = {
-- Tectonic Slam
[372735] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Chillweaver
[188067] = {
-- Ice Shield
[384933] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Ice Shield 2
[372749] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Ice Shield 3
[372743] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Meledrusa
[188252] = {
-- Frost Overload
[373680] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Frost Overload 2
[373688] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Cinderweaver
[190207] = {
-- Cinderbolt 1
[384194] = {
kickable = true, sweepable = false, paraliseable = false
},
-- Cinderbolt 2
[384197] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Flame Dancer
[190206] = {
-- Flame Dance
[385536] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Blazebound Firestorm
[189886] = {
-- Roaring Blaze
[373017] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Tempest Channeler
[198047] = {
-- Thunder Bolt
[392576] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Flame Channeler
[197985] = {
-- Flashfire
[392451] = {
kickable = true, sweepable = true, paraliseable = false
},
-- Flashfire
[392452] = {
kickable = true, sweepable = true, paraliseable = false
},
},
--
-- NO
--
-- Hornsounder
[192796] = {
-- Rally The Clan
[383823] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Plainstomper
[191847] = {
-- Disruptive Shout
[384365] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Lancemaster
[192800] = {
-- Disruptive Shout
[384365] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Arcblade
[194898] = {
-- Arcing Strike
[387135] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Stormcaller Botoo
[194317] = {
-- Stormbolt
[386012] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Thunderbeast
[195696] = {
-- Chain Lightning
[387127] = {
kickable = true, sweepable = false, paraliseable = false
}
},
[194894] = {
-- Tempest
[386025] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Tempest 2
[386024] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Summon Squall
[386015] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Stormbolt
[386012] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Squall
[194895] = {
-- Surge
[386026] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Stormcaller Arynga
[195265] = {
-- Stormbolt
[386012] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Nokhud Neophyte
[196263] = {
-- Stormbolt
[386012] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Stormcaller Solongo
[194315] = {
-- Stormbolt
[386012] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Stormcaller Zarii
[194316] = {
-- Stormbolt
[386012] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Soulharvester Galtmaa
[195927] = {
-- Death Bolt Volley
[387411] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Soulharvester Tumen
[195929] = {
-- Death Bolt Volley
[387411] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Soulharvester Mandakh
[195930] = {
-- Death Bolt Volley
[387411] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Soulharvester Duuren
[195928] = {
-- Death Bolt Volley
[387411] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Beastcaller
[195878] = {
-- Desecrated Roar
[387440] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Heavy Slash
[387826] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Mystic
[195877] = {
-- Swift Wind
[387596] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Risen Warrior
[195855] = {
-- Mortal Strike
[388801] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Teera
[186339] = {
-- Guardian Wind
[384808] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Batak
[193462] = {
-- Bloodcurdling Shout
[373395] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Balakar Add
[190294] = {
-- Storm Bolt
[376725] = {
kickable = true, sweepable = false, paraliseable = false
}
},
--
-- AV
--
-- Lasher
[196102] = {
-- Mystic Vapors
[387564] = {
kickable = false, sweepable = true, paraliseable = true
}
},
--Arcane Tender
[196115] = {
-- Erratic Growth
[375596] = {
kickable = true, sweepable = false, paraliseable = false
}
},
--Arcane Tender 2
[191164] = {
-- Erratic Growth
[375596] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Crystal Fury
[187160] = {
-- Shards
[370764] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Arcane Elemental
[186741] = {
-- Waking Bane
[386546] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Curator
[187154] = {
-- Heavy Tome
[389804] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Rune Seal Keeper
[187155] = {
-- Icy Bindings
[377488] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Crystal Fury
[196116] = {
-- Shards
[370764] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Vault Guard
[190510] = {
-- Ice Cutter
[377105] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Frog
[187246] = {
-- Null Stomp
[386526] = {
kickable = false, sweepable = true, paraliseable = false
}
},
--
-- AA
--
-- Scepter
[196576] = {
-- Mystic Blast
[396812] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Arcane Rain
[388886] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Textbook
[196044] = {
-- Lecture
[388392] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Eagle
[192333] = {
-- Call The Flock
[377389] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Echoknight
[196200] = {
-- Whirlwind
[387910] = {
kickable = false, sweepable = true, paraliseable = true
}
},
-- Invoker
[196202] = {
-- Arcane Missiles
[387975] = {
kickable = true, sweepable = true, paraliseable = true
}
},
--
-- SMBG
--
-- Bone Mender
[75713] = {
-- Shadow Mend
[152818] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Ritual Bones
[75715] = {
-- Void Slash
[164907] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Defiled Spirit
[75451] = {
-- Cry of Anguish
[398154] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Dominator
[76446] = {
-- Voidlash
[156776] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Spider
[76104] = {
-- Necrotic Burst
[156718] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Bat
[75459] = {
-- Plague Spit
[153524] = {
kickable = true, sweepable = true, paraliseable = true
}
},
--
-- TJS
--
-- Waterspeaker
[200126] = {
-- Hydrolance
[397888] = {
kickable = true, sweepable = true, paraliseable = true
},
-- Tidal Burst
[397889] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Wise Mari
[56448] = {
-- Hydrolance
[397801] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Haunting Sha
[59555] = {
-- Gaze
[114646] = {
kickable = false, sweepable = true, paraliseable = true
},
-- Scream
[398859] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Fish
[59546] = {
-- Sleepy
[395872] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Hozen
[59552] = {
-- Fit of Rage
[396018] = {
kickable = true, sweepable = false, paraliseable = false
}
},
-- Golden Beetle
[59545] = {
-- Staggering Blow
[396019] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Tiger
[59544] = {
-- Cat Nap
[396073] = {
kickable = true, sweepable = true, paraliseable = false
}
},
-- Mistweaver
[200137] = {
-- Defilling Mist
[397914] = {
kickable = true, sweepable = true, paraliseable = true
}
},
-- Guardian
[200131] = {
-- Leg Sweep
[397899] = {
kickable = false, sweepable = true, paraliseable = false
}
},
-- Minion of Doubt
[57109] = {
-- Dark Claw
[397931] = {
kickable = false, sweepable = true, paraliseable = false
}
}
}
return _InterruptList

@ -1,416 +0,0 @@
---@alias TankBuster { shouldZen: boolean | nil, shouldDampen: boolean | nil, shouldDiffuse: boolean | nil, shouldFort: boolean | nil, shouldBubble: boolean | nil, shouldPurify: boolean | nil }
---@alias TankBusterList table<number, table<number, TankBuster>>
---@type TankBusterList
local _TankBusterList = {
--
-- RLP
--
-- Primal Juggernaut
[188244] = {
-- Crushing Smash
[372730] = {
shouldDampen = true,
shouldPurify = true
}
},
-- Defier Draghar
[187897] = {
-- Steel Barrage
[372047] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Thunderhead
[197698] = {
-- Thunder Jaw
[392395] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldPurify = true,
shouldZen = true
}
},
-- Flamegullet
[197697] = {
-- Fire Maw
[392394] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldPurify = true,
shouldZen = true
}
},
-- Kokia
[189232] = {
-- Searing Blows
[372858] = {
shouldBubble = true,
shouldDampen = true,
shouldFort = true,
shouldPurify = true,
shouldZen = true
},
},
-- Erkhart
[190485] = {
-- Stormslam
[381512] = {
shouldPurify = true,
shouldZen = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true
}
},
--
-- NO
--
-- Raging Tempest
[186615] = {
-- Energy Surge
[384686] = {
shouldDampen = true,
shouldPurify = true,
shouldFort = true
}
},
-- Beastcaller
[195878] = {
-- Heavy Slash
[387826] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Maruuk
[186338] = {
-- Brutalize
[382836] = {
shouldDampen = true,
shouldZen = true,
shouldPurify = true
}
},
-- Balakar
[186151] = {
-- Rending Strike
[375937] = {
shouldDampen = true,
shouldZen = true,
shouldPurify = true
}
},
--
-- AV
--
-- Leymore
[186644] = {
-- Infused Strike
[374789] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldZen = true,
shouldPurify = true
}
},
-- Vault Guard
[190510] = {
-- Ice Cutter
[377105] = {
shouldPurify = true,
shouldDampen = true,
shouldDiffuse = true,
}
},
-- Scalebane
[191739] = {
-- Ice Cutter
[377105] = {
shouldPurify = true,
shouldDampen = true,
shouldDiffuse = true,
},
-- Spellfrost Breath
[391120] = {
shouldPurify = true,
shouldZen = true,
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
}
},
-- Azureblade
[186739] = {
-- Arcane Cleave
[372222] = {
shouldZen = true,
shouldPurify = true,
shouldDampen = true,
shouldDiffuse = true,
}
},
-- Umbreskul
[186738] = {
-- Dragon Strike
[384978] = {
shouldZen = true,
shouldPurify = true,
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true
}
},
--
-- AA
--
-- Battleaxe
[196577] = {
-- Severing Slash
[388911] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Crawth
[191736] = {
-- Savage Peck
[376997] = {
shouldBubble = true,
shouldDampen = true,
shouldZen = true,
shouldPurify = true,
shouldFort = true
}
},
-- Overgrown Ancient
[196482] = {
-- Barkbreaker
[388544] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true,
shouldFort = true
}
},
--
-- HOV
--
-- Storm Drake
[97068] = {
-- Lightning Breath
[198888] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldPurify = true
}
},
-- Hymdall
[94960] = {
-- Bloodletting Sweep
[193092] = {
shouldBubble = true,
shouldDampen = true,
shouldZen = true,
shouldPurify = true,
shouldFort = true
}
},
-- Hyrja
[95833] = {
[192018] = {
shouldZen = true,
shouldPurify = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true
}
},
-- Storm Drake
[99891] = {
-- Lightning Breath
[198888] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldPurify = true,
}
},
-- Angerhoof Bull
[96611] = {
-- Piercing Horns
[199151] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Steeljaw Grizzly
[96677] = {
-- Rending Claws
[185425] = {
shouldBubble = true,
shouldDampen = true,
shouldFort = true,
shouldPurify = true
}
},
-- King Ranulf
[97083] = {
-- Sever
[199652] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- King Haldor
[95843] = {
-- Sever
[199652] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- King Bjorn
[97081] = {
-- Sever
[199652] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- King Tor
[97084] = {
-- Sever
[199652] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- God-King Skovald
[95675] = {
-- Savaga Slash
[193668] = {
shouldBubble = true,
shouldDampen = true,
shouldZen = true,
shouldPurify = true
}
},
--
-- CoS
--
-- Sentry
[104270] = {
-- Charged Smash
[209495] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true,
}
},
-- Gereth the Vile
[108151] = {
-- Vampiric Claws
[373364] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldZen = true,
shouldPurify = true
}
},
--
-- SMBG
--
-- Reanimated Ritual Bones
[75715] = {
-- Shadow Slash
[75715] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true,
shouldPurify = true
}
},
--
-- TJS
--
-- Haunting Sha
[59555] = {
-- Haunting Gaze
[114646] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true,
shouldPurify = true
}
},
-- Songbird
[59553] = {
-- Vicious Peck
[396007] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = false,
shouldPurify = true
}
},
-- The Golden Beetle
[59545] = {
[396019] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Sha-Touched Guardian
[200131] = {
-- Setting Sun Kick
[397094] = {
shouldBubble = true,
shouldDampen = true,
shouldPurify = true
}
},
-- Minion of Doubt
[57109] = {
-- Dark Claw
[397931] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldPurify = true
}
},
-- Liu Flameheart
[56732] = {
-- Jade Serpent Strike
[106841] = {
shouldBubble = true,
shouldDampen = true,
shouldDiffuse = true,
shouldFort = true,
shouldPurify = true,
shouldZen = true
}
}
}
return _TankBusterList

@ -51,10 +51,16 @@ function Item:New(id)
self.ItemID = id self.ItemID = id
-- Register spell in spellbook -- Register spell in spellbook
local name, spellID = GetItemSpell(self:GetID()) local name, spellID
if C_Item.GetItemSpell then
name, spellID = C_Item.GetItemSpell(self:GetID())
else
name, spellID = GetItemSpell(self:GetID())
end
if spellID then if spellID then
self.spellID = spellID self.spellID = spellID
Bastion.SpellBook:GetSpell(spellID) Bastion.Globals.SpellBook:GetSpell(spellID)
end end
return self return self
@ -69,18 +75,27 @@ end
-- Get the Items name -- Get the Items name
---@return string ---@return string
function Item:GetName() function Item:GetName()
if C_Item.GetItemInfo then
return C_Item.GetItemInfo(self:GetID())
end
return GetItemInfo(self:GetID()) return GetItemInfo(self:GetID())
end end
-- Get the Items icon -- Get the Items icon
---@return number ---@return number
function Item:GetIcon() function Item:GetIcon()
if C_Item.GetItemIconByID then
return C_Item.GetItemIconByID(self:GetID())
end
return select(3, GetItemInfo(self:GetID())) return select(3, GetItemInfo(self:GetID()))
end end
-- Get the Items cooldown -- Get the Items cooldown
---@return number ---@return number
function Item:GetCooldown() function Item:GetCooldown()
if C_Item.GetItemCooldown then
return select(2, C_Item.GetItemCooldown(self:GetID()))
end
return select(2, C_Container.GetItemCooldown(self:GetID())) return select(2, C_Container.GetItemCooldown(self:GetID()))
end end
@ -105,6 +120,10 @@ end
-- Get the Items cooldown remaining -- Get the Items cooldown remaining
---@return number ---@return number
function Item:GetCooldownRemaining() function Item:GetCooldownRemaining()
if C_Item.GetItemCooldown then
local start, duration = C_Item.GetItemCooldown(self:GetID())
return start + duration - GetTime()
end
local start, duration = C_Container.GetItemCooldown(self:GetID()) local start, duration = C_Container.GetItemCooldown(self:GetID())
return start + duration - GetTime() return start + duration - GetTime()
end end
@ -131,7 +150,11 @@ function Item:Use(unit, condition)
self.wasLooking = IsMouselooking() self.wasLooking = IsMouselooking()
-- Use the Item -- Use the Item
if C_Item.UseItemByName then
C_Item.UseItemByName(self:GetName(), unit:GetOMToken())
else
UseItemByName(self:GetName(), unit:GetOMToken()) UseItemByName(self:GetName(), unit:GetOMToken())
end
Bastion:Debug("Using", self) Bastion:Debug("Using", self)
@ -161,18 +184,28 @@ end
-- Check if the Item is known -- Check if the Item is known
---@return boolean ---@return boolean
function Item:IsEquipped() function Item:IsEquipped()
if C_Item.IsEquippedItem then
return C_Item.IsEquippedItem(self:GetID())
end
return IsEquippedItem(self:GetID()) return IsEquippedItem(self:GetID())
end end
-- Check if the Item is on cooldown -- Check if the Item is on cooldown
---@return boolean ---@return boolean
function Item:IsOnCooldown() function Item:IsOnCooldown()
if C_Item.GetItemCooldown then
return select(2, C_Item.GetItemCooldown(self:GetID())) > 0
end
return select(2, C_Container.GetItemCooldown(self:GetID())) > 0 return select(2, C_Container.GetItemCooldown(self:GetID())) > 0
end end
-- Check if the Item is usable -- Check if the Item is usable
---@return boolean ---@return boolean
function Item:IsUsable() function Item:IsUsable()
if C_Item.IsUsableItem then
local usable, noMana = C_Item.IsUsableItem(self:GetID())
return usable or usableExcludes[self:GetID()]
end
local usable, noMana = IsUsableItem(self:GetID()) local usable, noMana = IsUsableItem(self:GetID())
return usable or usableExcludes[self:GetID()] return usable or usableExcludes[self:GetID()]
end end
@ -187,6 +220,9 @@ end
-- Is equippable -- Is equippable
---@return boolean ---@return boolean
function Item:IsEquippable() function Item:IsEquippable()
if C_Item.IsEquippableItem then
return C_Item.IsEquippableItem(self:GetID())
end
return IsEquippableItem(self:GetID()) return IsEquippableItem(self:GetID())
end end
@ -239,7 +275,7 @@ function Item:Click(x, y, z)
if type(x) == 'table' then if type(x) == 'table' then
x, y, z = x.x, x.y, x.z x, y, z = x.x, x.y, x.z
end end
if IsItemPending() == 64 then if IsSpellPending() == 64 then
MouselookStop() MouselookStop()
Click(x, y, z) Click(x, y, z)
if self:GetWasLooking() then if self:GetWasLooking() then
@ -302,7 +338,7 @@ end
-- Get the last use time -- Get the last use time
---@return number ---@return number
function Item:GetLastUseTime() function Item:GetLastUseTime()
return Bastion.SpellBook:GetSpell(self:GetID()):GetLastCastTime() return Bastion.Globals.SpellBook:GetSpell(self:GetID()):GetLastCastTime()
end end
-- Get time since last use -- Get time since last use
@ -317,6 +353,9 @@ end
-- Get the Items charges -- Get the Items charges
---@return number ---@return number
function Item:GetCharges() function Item:GetCharges()
if C_Item.GetItemCount then
return C_Item.GetItemCount(self:GetID())
end
return GetItemCharges(self:GetID()) return GetItemCharges(self:GetID())
end end
@ -430,7 +469,7 @@ end
---@return Spell | nil ---@return Spell | nil
function Item:GetSpell() function Item:GetSpell()
if self.spellID then if self.spellID then
return Bastion.SpellBook:GetSpell(self.spellID) return Bastion.Globals.SpellBook:GetSpell(self.spellID)
end end
return nil return nil

@ -0,0 +1,115 @@
local Tinkr, Bastion = ...
---@class Library
---@field name string
---@field dependencies table
---@field exports table
---@field resolved table
local Library = {
name = nil,
dependencies = {},
exports = {
default = function()
return nil
end
},
resolved = nil
}
Library.__index = Library
---@param name string
---@param library table
---@return Library
function Library:New(library)
local self = {
name = library.name or nil,
dependencies = {},
exports = library.exports or {
default = function()
return nil
end
},
resolved = nil
}
self = setmetatable(self, Library)
return self
end
function Library:ResolveExport(export)
if type(export) == 'function' then
return export(self)
end
return export
end
function Library:Resolve()
if not self.exports then
error("Library " .. self.name .. " has no exports")
end
if self.resolved then
if self.exports.default then
return self.resolved[1], self.resolved[2]
end
return unpack(self.resolved)
end
if self.exports.default then
-- return default first if it exists
local default = self.exports.default
local remaining = {}
for k, v in pairs(self.exports) do
if k ~= 'default' then
remaining[k] = self:ResolveExport(v)
end
end
self.resolved = {self:ResolveExport(default), remaining}
return self.resolved[1], self.resolved[2]
end
self.resolved = {}
for k, v in pairs(self.exports) do
self.resolved[k] = self:ResolveExport(v)
end
return unpack(self.resolved)
end
function Library:DependsOn(other)
for _, dependency in pairs(self.dependencies) do
if dependency == other then
return true
end
end
return false
end
---@param library string
function Library:Import(library)
local lib = Bastion:GetLibrary(library)
if not lib then
error("Library " .. library .. " does not exist")
end
if not table.contains(self.dependencies, library) then
table.insert(self.dependencies, library)
end
if lib:DependsOn(self.name) then
error("Circular dependency detected between " .. self.name .. " and " .. library)
end
return lib:Resolve()
end
return Library

@ -1,5 +1,3 @@
local Tinkr, Bastion = ...
---@class List ---@class List
local List = { local List = {
-- Add overload -- Add overload
@ -90,9 +88,9 @@ function List:each(callback)
end end
---@param callback fun(value: any): boolean ---@param callback fun(value: any): boolean
---@return boolean ---@return List
function List:map(callback) function List:map(callback)
local newList = List.new() local newList = List:New()
for _, v in ipairs(self._list) do for _, v in ipairs(self._list) do
newList:push(callback(v)) newList:push(callback(v))
end end
@ -100,9 +98,9 @@ function List:map(callback)
end end
---@param callback fun(value: any): boolean ---@param callback fun(value: any): boolean
---@return boolean ---@return List
function List:filter(callback) function List:filter(callback)
local newList = List.new() local newList = List:New()
for _, v in ipairs(self._list) do for _, v in ipairs(self._list) do
if callback(v) then if callback(v) then
newList:push(v) newList:push(v)
@ -111,13 +109,17 @@ function List:filter(callback)
return newList return newList
end end
---@param callback fun(value: any): boolean ---@param callback fun(result: any, value: any): boolean
---@param initialValue any ---@param initialValue any
---@return boolean ---@return boolean
function List:reduce(callback, initialValue) function List:reduce(callback, initialValue)
local result = initialValue local result = initialValue
local done = false
for _, v in ipairs(self._list) do for _, v in ipairs(self._list) do
result = callback(result, v) result, done = callback(result, v)
if done then
break
end
end end
return result return result
end end
@ -144,7 +146,7 @@ function List:findIndex(callback)
return nil return nil
end end
---@param callback fun(value: any): boolean ---@param callback fun(...): boolean
---@return nil ---@return nil
function List:sort(callback) function List:sort(callback)
table.sort(self._list, callback) table.sort(self._list, callback)
@ -152,7 +154,7 @@ end
---@return List ---@return List
function List:reverse() function List:reverse()
local newList = List.new() local newList = List:New()
for i = #self._list, 1, -1 do for i = #self._list, 1, -1 do
newList:push(self._list[i]) newList:push(self._list[i])
end end
@ -161,7 +163,7 @@ end
---@return List ---@return List
function List:clone() function List:clone()
local newList = List.new() local newList = List:New()
for _, v in ipairs(self._list) do for _, v in ipairs(self._list) do
newList:push(v) newList:push(v)
end end
@ -171,7 +173,7 @@ end
---@param list List ---@param list List
---@return List ---@return List
function List:concat(list) function List:concat(list)
local newList = List.new() local newList = List:New()
for _, v in ipairs(self._list) do for _, v in ipairs(self._list) do
newList:push(v) newList:push(v)
end end

@ -0,0 +1,585 @@
local Tinkr, Bastion = ...
---@class MythicPlusUtils
local MythicPlusUtils = {
debuffLogging = false,
castLogging = false,
random = '',
loggedCasts = {},
loggedDebuffs = {},
kickList = {},
aoeBosses = {}
}
MythicPlusUtils.__index = MythicPlusUtils
---@return MythicPlusUtils
function MythicPlusUtils:New()
local self = setmetatable({}, MythicPlusUtils)
---@diagnostic disable-next-line: assign-type-mismatch
self.random = math.random(1000000, 9999999)
self.aoeBosses = {
[196482] = true,
[188252] = true,
[186644] = true,
[104217] = true,
}
self.tankBusters = {
[397931] = true, -- https://www.wowhead.com/spell=397931/dark-claw
[396019] = true, -- https://www.wowhead.com/spell=396019/staggering-blow
[372730] = true, -- https://www.wowhead.com/spell=372730/crushing-smash
[395303] = true, -- https://www.wowhead.com/spell=395303/thunder-jaw
[392395] = true, -- https://www.wowhead.com/spell=392395/thunder-jaw
[372858] = true, -- https://www.wowhead.com/spell=372858/searing-blows
[372859] = true, -- https://www.wowhead.com/spell=372859/searing-blows
[387135] = true, -- https://www.wowhead.com/spell=387135/arcing-strike
[388801] = true, -- https://www.wowhead.com/spell=388801/mortal-strike
[387826] = true, -- https://www.wowhead.com/spell=387826/heavy-slash
[370764] = true, -- https://www.wowhead.com/spell=370764/piercing-shards
[377105] = true, -- https://www.wowhead.com/spell=377105/ice-cutter
[388911] = true, -- https://www.wowhead.com/spell=388911/severing-slash
[388912] = true, -- https://www.wowhead.com/spell=388912/severing-slash
[199050] = true, -- https://www.wowhead.com/spell=199050/mortal-hew
[164907] = true, -- https://www.wowhead.com/spell=164907/void-slash
[377991] = true, -- https://www.wowhead.com/spell=377991/storm-slash
[376997] = true, -- https://www.wowhead.com/spell=376997/savage-peck
[192018] = true, -- https://www.wowhead.com/spell=192018/shield-of-light
[106823] = true, -- https://www.wowhead.com/spell=106823/serpent-strike
[106841] = true, -- https://www.wowhead.com/spell=106841/jade-serpent-strike
[381512] = true, -- https://www.wowhead.com/spell=381512/stormslam
[381514] = true, -- https://www.wowhead.com/spell=381514/stormslam
[381513] = true, -- https://www.wowhead.com/spell=381513/stormslam
[381515] = true, -- https://www.wowhead.com/spell=381515/stormslam
[372222] = true, -- https://www.wowhead.com/spell=372222/arcane-cleave
[385958] = true, -- https://www.wowhead.com/spell=385958/arcane-expulsion
[382836] = true, -- https://www.wowhead.com/spell=382836/brutalize
[376827] = true, -- https://www.wowhead.com/spell=376827/conductive-strike not sure if we defensive on these or the other strike NO final boss
[375937] = true, -- https://www.wowhead.com/spell=375937/rending-strike not sure if we defensive on these or the other strike NO final boss
[198888] = true, -- https://www.wowhead.com/spell=198888/lightning-breath
[384978] = true, -- https://www.wowhead.com/spell=384978/dragon-strike
[388923] = true, -- https://www.wowhead.com/spell=388923/burst-forth
}
self.kickList = {
-- 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
}
},
[195696] = {
[387125] = {
true, false, false
}
}
}
Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras)
if not self.debuffLogging then
return
end
if auras.addedAuras then
local addedAuras = auras.addedAuras
if #addedAuras > 0 then
for i = 1, #addedAuras do
local aura = Bastion.Aura:CreateFromUnitAuraInfo(addedAuras[i])
if not self.loggedDebuffs[aura:GetSpell():GetID()] and not aura:IsBuff() then
WriteFile('bastion-MPlusDebuffs-' .. self.random .. '.lua', [[
AuraName: ]] .. aura:GetName() .. [[
AuraID: ]] .. aura:GetSpell():GetID() .. "\n" .. [[
]], true)
end
end
end
end
end)
Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_SPELLCAST_START', function(unitTarget, castGUID, spellID)
if not self.castLogging then
return
end
if self.loggedCasts[spellID] then
return
end
local name
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(spellID)
name = info and info.name or ''
else
name = GetSpellInfo(spellID)
end
self.loggedCasts[spellID] = true
WriteFile('bastion-MPlusCasts-' .. self.random .. '.lua', [[
CastName: ]] .. name .. [[
CastID: ]] .. spellID .. "\n" .. [[
]], true)
end)
Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_SPELLCAST_CHANNEL_START', function(unitTarget, castGUID, spellID)
if not self.castLogging then
return
end
if self.loggedCasts[spellID] then
return
end
local name
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(spellID)
name = info and info.name or ''
else
name = GetSpellInfo(spellID)
end
self.loggedCasts[spellID] = true
WriteFile('bastion-MPlusCasts-' .. self.random .. '.lua', [[
CastName: ]] .. name .. [[
CastID: ]] .. spellID .. "\n" .. [[
]], true)
end)
return self
end
---@return nil
function MythicPlusUtils:ToggleDebuffLogging()
self.debuffLogging = not self.debuffLogging
end
---@return nil
function MythicPlusUtils:ToggleCastLogging()
self.castLogging = not self.castLogging
end
---@param unit Unit
---@param percent number
---@return boolean
function MythicPlusUtils:CastingCriticalKick(unit, percent)
local castingSpell = unit:GetCastingOrChannelingSpell()
if castingSpell then
local spellID = castingSpell:GetID()
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
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 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
return true
end
end
return false
end
---@param unit Unit
---@return boolean
function MythicPlusUtils:IsAOEBoss(unit)
return self.aoeBosses[unit:GetID()]
end
return MythicPlusUtils

@ -1,6 +1,11 @@
local Tinkr, Bastion = ... local Tinkr, Bastion = ...
---@class ObjectManager ---@class ObjectManager
---@field _lists table
---@field enemies List
---@field friends List
---@field activeEnemies List
---@field explosives List
local ObjectManager = {} local ObjectManager = {}
ObjectManager.__index = ObjectManager ObjectManager.__index = ObjectManager
@ -20,7 +25,7 @@ end
-- Register a custom list with a callback -- Register a custom list with a callback
---@param name string ---@param name string
---@param cb function ---@param cb function
---@return List ---@return List | false
function ObjectManager:RegisterList(name, cb) function ObjectManager:RegisterList(name, cb)
if self._lists[name] then if self._lists[name] then
return false return false
@ -88,6 +93,7 @@ function ObjectManager:Refresh()
self.friends:push(unit) self.friends:push(unit)
elseif unit:IsEnemy() then elseif unit:IsEnemy() then
self.enemies:push(unit) self.enemies:push(unit)
if unit:InCombatOdds() > 80 then if unit:InCombatOdds() > 80 then
self.activeEnemies:push(unit) self.activeEnemies:push(unit)
end end

@ -1,6 +1,8 @@
-- Create a sequencer class that takes a table of actions and executes them in order -- Create a sequencer class that takes a table of actions and executes them in order
---@class Sequencer ---@class Sequencer
---@field resetCondition fun(): boolean
---@field abortCondition fun(): boolean
---@field actions fun(sequencer: Sequencer)[]
local Sequencer = {} local Sequencer = {}
Sequencer.__index = Sequencer Sequencer.__index = Sequencer
@ -42,12 +44,10 @@ end
---@return boolean ---@return boolean
function Sequencer:Next() function Sequencer:Next()
if self:Finished() then if self:Finished() then
print("Its finished?")
return false return false
end end
local action = self.actions[self.index] local action = self.actions[self.index]
print("Attempting action: " .. self.index .. "")
if action(self) then if action(self) then
self.index = self.index + 1 self.index = self.index + 1
return true return true

@ -12,10 +12,11 @@ local Spell = {
lastCastAt = false, lastCastAt = false,
conditions = {}, conditions = {},
target = false, target = false,
release_at = false
} }
local usableExcludes = { local usableExcludes = {
[18562] = true, [18562] = true
} }
function Spell:__index(k) function Spell:__index(k)
@ -56,6 +57,12 @@ function Spell:New(id)
return self return self
end end
-- Duplicator
---@return Spell
function Spell:Fresh()
return Spell:New(self:GetID())
end
-- Get the spells id -- Get the spells id
---@return number ---@return number
function Spell:GetID() function Spell:GetID()
@ -73,21 +80,70 @@ end
-- Get the spells name -- Get the spells name
---@return string ---@return string
function Spell:GetName() function Spell:GetName()
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(self:GetID())
return info and info.name or nil
end
return GetSpellInfo(self:GetID()) return GetSpellInfo(self:GetID())
end end
-- Get the spells icon -- Get the spells icon
---@return number ---@return number
function Spell:GetIcon() function Spell:GetIcon()
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(self:GetID())
return info and info.iconID or nil
end
return select(3, GetSpellInfo(self:GetID())) return select(3, GetSpellInfo(self:GetID()))
end end
-- Get the spells cooldown -- Get the spells cooldown
---@return number ---@return number
function Spell:GetCooldown() function Spell:GetCooldown()
if C_Spell.GetSpellCooldown then
local info = C_Spell.GetSpellCooldown(self:GetID())
return info and info.duration or nil
end
return select(2, GetSpellCooldown(self:GetID())) return select(2, GetSpellCooldown(self:GetID()))
end end
-- Get the full cooldown (time until all charges are available)
---@return number
function Spell:GetFullRechargeTime()
if C_Spell.GetSpellCooldown then
local info = C_Spell.GetSpellCooldown(self:GetID())
if info.isEnabled == 0 then
return 0
end
local chargeInfo = C_Spell.GetSpellCharges(self:GetID())
if chargeInfo.currentCharges == chargeInfo.maxCharges then
return 0
end
if chargeInfo.currentCharges == 0 then
return info.startTime + info.duration - GetTime()
end
return chargeInfo.cooldownStartTime + chargeInfo.cooldownDuration - GetTime()
end
local start, duration, enabled = GetSpellCooldown(self:GetID())
if enabled == 0 then
return 0
end
local charges, maxCharges, chargeStart, chargeDuration = GetSpellCharges(self:GetID())
if charges == maxCharges then
return 0
end
if charges == 0 then
return start + duration - GetTime()
end
return chargeStart + chargeDuration - GetTime()
end
-- Return the castable function -- Return the castable function
---@return fun(self:Spell):boolean ---@return fun(self:Spell):boolean
function Spell:GetCastableFunction() function Spell:GetCastableFunction()
@ -109,10 +165,23 @@ end
-- Get the spells cooldown remaining -- Get the spells cooldown remaining
---@return number ---@return number
function Spell:GetCooldownRemaining() function Spell:GetCooldownRemaining()
if C_Spell.GetSpellCooldown then
local info = C_Spell.GetSpellCooldown(self:GetID())
return info and info.startTime + info.duration - GetTime() or nil
end
local start, duration = GetSpellCooldown(self:GetID()) local start, duration = GetSpellCooldown(self:GetID())
return start + duration - GetTime() return start + duration - GetTime()
end end
-- Get the spell count
---@return number
function Spell:GetCount()
if C_Spell.GetSpellCastCount then
return C_Spell.GetSpellCastCount(self:GetID())
end
return GetSpellCount(self:GetID())
end
-- On cooldown -- On cooldown
---@return boolean ---@return boolean
function Spell:OnCooldown() function Spell:OnCooldown()
@ -128,12 +197,16 @@ end
-- Cast the spell -- Cast the spell
---@param unit Unit ---@param unit Unit
---@param condition? string ---@param condition? string|function
---@return boolean ---@return boolean
function Spell:Cast(unit, condition) function Spell:Cast(unit, condition)
if condition and not self:EvaluateCondition(condition) then if condition then
if type(condition) == "string" and not self:EvaluateCondition(condition) then
return false
elseif type(condition) == "function" and not condition(self) then
return false return false
end end
end
if not self:Castable() then if not self:Castable() then
return false return false
@ -215,6 +288,8 @@ end
-- Check if the spell is known -- Check if the spell is known
---@return boolean ---@return boolean
function Spell:IsKnown() function Spell:IsKnown()
local IsSpellKnown = C_Spell.IsSpellKnown and C_Spell.IsSpellKnown or IsSpellKnown
local IsPlayerSpell = C_Spell.IsPlayerSpell and C_Spell.IsPlayerSpell or IsPlayerSpell
local isKnown = IsSpellKnown(self:GetID()) local isKnown = IsSpellKnown(self:GetID())
local isPlayerSpell = IsPlayerSpell(self:GetID()) local isPlayerSpell = IsPlayerSpell(self:GetID())
return isKnown or isPlayerSpell return isKnown or isPlayerSpell
@ -223,12 +298,20 @@ end
-- Check if the spell is on cooldown -- Check if the spell is on cooldown
---@return boolean ---@return boolean
function Spell:IsOnCooldown() function Spell:IsOnCooldown()
if C_Spell.GetSpellCooldown then
local info = C_Spell.GetSpellCooldown(self:GetID())
return info and info.duration > 0
end
return select(2, GetSpellCooldown(self:GetID())) > 0 return select(2, GetSpellCooldown(self:GetID())) > 0
end end
-- Check if the spell is usable -- Check if the spell is usable
---@return boolean ---@return boolean
function Spell:IsUsable() function Spell:IsUsable()
if C_Spell.IsSpellUsable then
local usable, noMana = C_Spell.IsSpellUsable(self:GetID())
return usable or usableExcludes[self:GetID()] and not noMana
end
local usable, noMana = IsUsableSpell(self:GetID()) local usable, noMana = IsUsableSpell(self:GetID())
return usable or usableExcludes[self:GetID()] and not noMana return usable or usableExcludes[self:GetID()] and not noMana
end end
@ -280,9 +363,9 @@ function Spell:GetWasLooking()
end end
-- Click the spell -- Click the spell
---@param x number ---@param x number|Vector3
---@param y number ---@param y? number
---@param z number ---@param z? number
---@return boolean ---@return boolean
function Spell:Click(x, y, z) function Spell:Click(x, y, z)
if type(x) == 'table' then if type(x) == 'table' then
@ -313,13 +396,29 @@ end
-- Check if the spell is castable and cast it -- Check if the spell is castable and cast it
---@return boolean ---@return boolean
function Spell:HasRange() function Spell:HasRange()
if C_Spell.SpellHasRange then
return C_Spell.SpellHasRange(self:GetID())
end
return SpellHasRange(self:GetName()) return SpellHasRange(self:GetName())
end end
-- Get the range of the spell
---@return number
---@return number
function Spell:GetRange()
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(self:GetID())
return info and info.minRange or nil, info and info.maxRange or nil
end
local name, rank, icon, castTime, minRange, maxRange, spellID, originalIcon = GetSpellInfo(self:GetID())
return maxRange, minRange
end
-- Check if the spell is in range of the unit -- Check if the spell is in range of the unit
---@param unit Unit ---@param unit Unit
---@return boolean ---@return boolean
function Spell:IsInRange(unit) function Spell:IsInRange(unit)
local IsSpellInRange = C_Spell.IsSpellInRange and C_Spell.IsSpellInRange or IsSpellInRange
local hasRange = self:HasRange() local hasRange = self:HasRange()
local inRange = IsSpellInRange(self:GetName(), unit:GetOMToken()) local inRange = IsSpellInRange(self:GetName(), unit:GetOMToken())
@ -361,16 +460,50 @@ end
-- Get the spells charges -- Get the spells charges
---@return number ---@return number
function Spell:GetCharges() function Spell:GetCharges()
if C_Spell.GetSpellCharges then
local info = C_Spell.GetSpellCharges(self:GetID())
return info and info.currentCharges or nil
end
return GetSpellCharges(self:GetID()) return GetSpellCharges(self:GetID())
end end
function Spell:GetMaxCharges() function Spell:GetMaxCharges()
if C_Spell.GetSpellCharges then
local info = C_Spell.GetSpellCharges(self:GetID())
return info and info.maxCharges or nil
end
return select(2, GetSpellCharges(self:GetID())) return select(2, GetSpellCharges(self:GetID()))
end end
function Spell:GetCastLength()
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(self:GetID())
return info and info.castTime or nil
end
return select(4, GetSpellInfo(self:GetID()))
end
-- Get the spells charges -- Get the spells charges
---@return number ---@return number
function Spell:GetChargesFractional() function Spell:GetChargesFractional()
if C_Spell.GetSpellCharges then
local info = C_Spell.GetSpellCharges(self:GetID())
if info.currentCharges == info.maxCharges then
return info.maxCharges
end
if info.currentCharges == 0 then
return 0
end
local timeSinceStart = GetTime() - info.cooldownStartTime
local timeLeft = info.cooldownDuration - timeSinceStart
local timePerCharge = info.cooldownDuration / info.maxCharges
local chargesFractional = info.currentCharges + (timeLeft / timePerCharge)
return chargesFractional
end
local charges, maxCharges, start, duration = GetSpellCharges(self:GetID()) local charges, maxCharges, start, duration = GetSpellCharges(self:GetID())
if charges == maxCharges then if charges == maxCharges then
@ -392,22 +525,14 @@ end
-- Get the spells charges remaining -- Get the spells charges remaining
---@return number ---@return number
function Spell:GetChargesRemaining() function Spell:GetChargesRemaining()
if C_Spell.GetSpellCharges then
local info = C_Spell.GetSpellCharges(self:GetID())
return info and info.currentCharges or nil
end
local charges, maxCharges, start, duration = GetSpellCharges(self:GetID()) local charges, maxCharges, start, duration = GetSpellCharges(self:GetID())
return charges return charges
end end
-- Get a spells charge cooldown if applicable
---@return number | nil
function Spell:GetNextChargeCooldown()
local charges, maxCharges, start, duration = GetSpellCharges(self:GetID())
if not maxCharges then return nil end
local nextChargeTime = start + duration
return nextChargeTime - GetTime()
end
-- Create a condition for the spell -- Create a condition for the spell
---@param name string ---@param name string
---@param func fun(self:Spell):boolean ---@param func fun(self:Spell):boolean
@ -496,9 +621,7 @@ end
-- IsDiseaseDispel -- IsDiseaseDispel
---@return boolean ---@return boolean
function Spell:IsDiseaseDispel() function Spell:IsDiseaseDispel()
return ({ return ({})[self:GetID()]
})[self:GetID()]
end end
-- IsSpell -- IsSpell
@ -508,37 +631,21 @@ function Spell:IsSpell(spell)
return self:GetID() == spell:GetID() return self:GetID() == spell:GetID()
end end
---@alias Missile table<{ spellId: number, target: string, source: number, my: number, mx: number, mz: number, px: number, py: number, pz: number, uy: number, ux: number, uz: number, hx: number, hy: number, hz: number, iy: number, ix: number, iz: number, px: number, py: number, pz: number }> -- GetCost
---@return number
-- IsInFlight function Spell:GetCost()
---@return Missile | nil if C_Spell.GetSpellPowerCost then
function Spell:Missile() local info = C_Spell.GetSpellPowerCost(self:GetID())
local inFlight = nil return info and info.cost or 0
local missiles = Missiles()
for i, missile in ipairs(missiles) do
if missile.spellId == self:GetID() then
inFlight = missile
end
end end
local cost = GetSpellPowerCost(self:GetID())
return inFlight return cost and cost.cost or 0
end end
---@param unit Unit -- IsFree
---@return boolean ---@return boolean
function Spell:IsInFlightToUnit(unit) function Spell:IsFree()
local missile = self:Missile() return self:GetCost() == 0
local isInFlight = false
if missile then
if tostring(missile.target) == tostring(unit:GetGUID()) then
isInFlight = true
end
end
return isInFlight
end end
return Spell return Spell

@ -27,16 +27,31 @@ end
---@return Spell, ... Spell ---@return Spell, ... Spell
function SpellBook:GetSpells(...) function SpellBook:GetSpells(...)
local spells = {} local spells = {}
for _, id in ipairs({ ... }) do for _, id in ipairs({...}) do
table.insert(spells, self:GetSpell(id)) table.insert(spells, self:GetSpell(id))
end end
return unpack(spells) return unpack(spells)
end end
---@param ... number[]
---@return List
function SpellBook:GetList(...)
local spells = {}
for _, id in ipairs({...}) do
table.insert(spells, self:GetSpell(id))
end
return Bastion.List:New(spells)
end
---@param name string ---@param name string
---@return Spell ---@return Spell
function SpellBook:GetSpellByName(name) function SpellBook:GetSpellByName(name)
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(name)
return self:GetSpell(info.spellID)
end
local _, rank, icon, castTime, minRange, maxRange, spellID, originalIcon = GetSpellInfo(name) local _, rank, icon, castTime, minRange, maxRange, spellID, originalIcon = GetSpellInfo(name)
return self:GetSpell(spellID) return self:GetSpell(spellID)
end end

@ -1,104 +0,0 @@
local
Tinkr,
---@type Bastion
Bastion = ...
---@alias Toggle { x: number, icon: number, state: boolean, id: string, texture?: Texture }
local BUTTON_SIZE = 54
---@class ToggleManager
local ToggleManager = {
---@type table<string, Toggle>
toggles = {}
}
---@return number
function ToggleManager:NumActiveToggles()
local count = 0
for i, toggle in pairs(self.toggles) do
count = count + 1
end
return count
end
---@param newToggle { icon: number, state: boolean, id: string }
function ToggleManager:Add(newToggle)
if self.toggles[newToggle.id] then
self.toggles[newToggle.id].icon = newToggle.icon
self.toggles[newToggle.id].state = newToggle.state
else
local highestX = nil
for i, toggle in pairs(self.toggles) do
if highestX == nil or toggle.x > highestX then
highestX = toggle.x
end
end
if highestX == nil then
highestX = 2
else
highestX = highestX + BUTTON_SIZE
end
self.toggles[newToggle.id] = {
x = highestX,
state = newToggle.state,
icon = newToggle.icon,
id = newToggle.id
}
end
end
function ToggleManager:Refresh()
for i, toggle in pairs(self.toggles) do
if not toggle.texture then
local frame = CreateFrame("Frame", nil, UIParent)
frame:SetSize(BUTTON_SIZE, BUTTON_SIZE)
frame:SetPoint("BOTTOMLEFT", toggle.x, 230)
local Texture = frame:CreateTexture()
Texture:SetAllPoints(frame)
Texture:SetTexture(toggle.icon)
if not toggle.state then
Texture:SetDesaturated(true)
end
self.toggles[toggle.id].texture = Texture
end
-- else
-- local desaturation = toggle.texture:GetDesaturation()
-- if not toggle.state and desaturation == 0 then
-- toggle.texture:SetDesaturated(true)
-- elseif toggle.state and desaturation == 1 then
-- toggle.texture:SetDesaturated(false)
-- end
-- end
end
end
---@param id string
function ToggleManager:Toggle(id)
if self.toggles[id] then
self.toggles[id].state = not self.toggles[id].state
local desaturation = self.toggles[id].texture:GetDesaturation()
if not self.toggles[id].state and desaturation == 0 then
self.toggles[id].texture:SetDesaturated(true)
elseif self.toggles[id].state and desaturation == 1 then
self.toggles[id].texture:SetDesaturated(false)
end
end
end
---@param id string
---@return boolean
function ToggleManager:GetState(id)
if self.toggles[id] then return self.toggles[id].state else return false end
end
return ToggleManager

@ -13,7 +13,7 @@ local Unit = {
last_off_attack = 0, last_off_attack = 0,
last_main_attack = 0, last_main_attack = 0,
last_combat_time = 0, last_combat_time = 0,
ttd_ticker = 0, ttd_ticker = false,
ttd = 0, ttd = 0,
id = false, id = false,
} }
@ -114,6 +114,24 @@ function Unit:GetHP()
return self:GetHealth() / self:GetMaxHealth() * 100 return self:GetHealth() / self:GetMaxHealth() * 100
end end
-- Get realized health
---@return number
function Unit:GetRealizedHealth()
return self:GetHealth() - self:GetHealAbsorbedHealth()
end
-- get realized health percentage
---@return number
function Unit:GetRealizedHP()
return self:GetRealizedHealth() / self:GetMaxHealth() * 100
end
-- Get the abosorbed unit health
---@return number
function Unit:GetHealAbsorbedHealth()
return UnitGetTotalHealAbsorbs(self:GetOMToken())
end
-- Get the units health deficit -- Get the units health deficit
---@return number ---@return number
function Unit:GetHealthPercent() function Unit:GetHealthPercent()
@ -372,25 +390,62 @@ function Unit:IsCasting()
return UnitCastingInfo(self:GetOMToken()) ~= nil return UnitCastingInfo(self:GetOMToken()) ~= nil
end end
function Unit:GetTimeCastIsAt(percent)
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
if not name then
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self
:GetOMToken())
end
if name and startTimeMS and endTimeMS then
local castLength = endTimeMS - startTimeMS
local startTime = startTimeMS / 1000
local timeUntil = (castLength / 1000) * (percent / 100)
return startTime + timeUntil
end
return 0
end
-- Get Casting or channeling spell -- Get Casting or channeling spell
---@return Spell | nil ---@return Spell | nil
function Unit:GetCastingOrChannelingSpell() function Unit:GetCastingOrChannelingSpell()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self self:GetOMToken())
.unit)
if not name then if not name then
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self
:unit()) :GetOMToken())
end end
if name then if name then
return Bastion.SpellBook:GetSpell(spellId) return Bastion.Globals.SpellBook:GetSpell(spellId)
end end
return nil return nil
end end
-- Get the end time of the cast or channel
---@return number
function Unit:GetCastingOrChannelingEndTime()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
if not name then
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self
:GetOMToken())
end
if name then
return endTimeMS / 1000
end
return 0
end
-- Check if the unit is channeling a spell -- Check if the unit is channeling a spell
---@return boolean ---@return boolean
function Unit:IsChanneling() function Unit:IsChanneling()
@ -413,12 +468,11 @@ end
---@return number ---@return number
function Unit:GetChannelOrCastPercentComplete() function Unit:GetChannelOrCastPercentComplete()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self self:GetOMToken())
.unit)
if not name then if not name then
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self
:unit()) :GetOMToken())
end end
if name and startTimeMS and endTimeMS then if name and startTimeMS and endTimeMS then
@ -435,12 +489,11 @@ end
---@return boolean ---@return boolean
function Unit:IsInterruptible() function Unit:IsInterruptible()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self self:GetOMToken())
.unit)
if not name then if not name then
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self.unit name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId = UnitChannelInfo(self
:unit()) :GetOMToken())
end end
if name then if name then
@ -481,7 +534,7 @@ function Unit:GetEnemies(range)
local count = 0 local count = 0
Bastion.UnitManager:EnumEnemies(function(unit) Bastion.UnitManager:EnumEnemies(function(unit)
if not self:IsUnit(unit) and unit:GetDistance(self) <= range and unit:IsAlive() and self:CanSee(unit) and if not self:IsUnit(unit) and self:IsWithinCombatDistance(unit, range) and unit:IsAlive() and self:CanSee(unit) and
unit:IsEnemy() then unit:IsEnemy() then
count = count + 1 count = count + 1
end end
@ -503,7 +556,7 @@ function Unit:GetMeleeAttackers()
Bastion.UnitManager:EnumEnemies(function(unit) Bastion.UnitManager:EnumEnemies(function(unit)
if not self:IsUnit(unit) and unit:IsAlive() and self:CanSee(unit) and if not self:IsUnit(unit) and unit:IsAlive() and self:CanSee(unit) and
self:InMelee(unit) and unit:IsEnemy() and unit:InCombatOdds() > 80 then self:InMelee(unit) and unit:IsEnemy() then
count = count + 1 count = count + 1
end end
end) end)
@ -543,7 +596,7 @@ end
---@param unit Unit | nil ---@param unit Unit | nil
---@return number ---@return number
function Unit:GetComboPoints(unit) function Unit:GetComboPoints(unit)
if Tinkr.classic then if Tinkr.classic or Tinkr.era then
if not unit then if not unit then
return 0 return 0
end end
@ -554,7 +607,7 @@ end
---@return number ---@return number
function Unit:GetComboPointsMax() function Unit:GetComboPointsMax()
if Tinkr.classic then if Tinkr.classic or Tinkr.era then
return 5 return 5
end end
return UnitPowerMax(self:GetOMToken(), 4) return UnitPowerMax(self:GetOMToken(), 4)
@ -564,7 +617,7 @@ end
---@param unit Unit | nil ---@param unit Unit | nil
---@return number ---@return number
function Unit:GetComboPointsDeficit(unit) function Unit:GetComboPointsDeficit(unit)
if Tinkr.classic then if Tinkr.classic or Tinkr.era then
return self:GetComboPointsMax() - self:GetComboPoints(unit) return self:GetComboPointsMax() - self:GetComboPoints(unit)
end end
return self:GetComboPointsMax() - self:GetComboPoints() return self:GetComboPointsMax() - self:GetComboPoints()
@ -588,17 +641,12 @@ end
-- IsFacing -- IsFacing
---@param unit Unit ---@param unit Unit
---@param fov? number
---@return boolean ---@return boolean
function Unit:IsFacing(unit, fov) function Unit:IsFacing(unit)
local rot = ObjectRotation(self:GetOMToken()) local rot = ObjectRotation(self:GetOMToken())
local x, y, z = ObjectPosition(self:GetOMToken()) local x, y, z = ObjectPosition(self:GetOMToken())
local x2, y2, z2 = ObjectPosition(unit:GetOMToken()) local x2, y2, z2 = ObjectPosition(unit:GetOMToken())
local fovToUse = 90
if fov then fovToUse = fov end
if not x or not x2 or not rot then if not x or not x2 or not rot then
return false return false
end end
@ -790,7 +838,7 @@ function Unit:TimeToDie()
self.regression_history = {} self.regression_history = {}
if self.ttd_ticker then if self.ttd_ticker then
self.ttd_ticker:Cancel() self.ttd_ticker:Cancel()
self.ttd_ticker = nil self.ttd_ticker = false
end end
return 0 return 0
end end
@ -878,13 +926,12 @@ end
-- IsStealthed -- IsStealthed
---@return boolean ---@return boolean
function Unit:IsStealthed() function Unit:IsStealthed()
local Stealth = Bastion.SpellBook:GetSpell(1784) local Stealth = Bastion.Globals.SpellBook:GetSpell(1784)
local Vanish = Bastion.SpellBook:GetSpell(1856) local Vanish = Bastion.Globals.SpellBook:GetSpell(1856)
local ShadowDance = Bastion.SpellBook:GetSpell(185422) local ShadowDance = Bastion.Globals.SpellBook:GetSpell(185422)
local Subterfuge = Bastion.SpellBook:GetSpell(115192) local Subterfuge = Bastion.Globals.SpellBook:GetSpell(115192)
local Shadowmeld = Bastion.SpellBook:GetSpell(58984) local Shadowmeld = Bastion.Globals.SpellBook:GetSpell(58984)
local Sepsis = Bastion.SpellBook:GetSpell(328305) local Sepsis = Bastion.Globals.SpellBook:GetSpell(328305)
return self:GetAuras():FindAny(Stealth) or self:GetAuras():FindAny(ShadowDance) return self:GetAuras():FindAny(Stealth) or self:GetAuras():FindAny(ShadowDance)
end end
@ -912,7 +959,7 @@ end
---@return nil ---@return nil
function Unit:WatchForSwings() function Unit:WatchForSwings()
Bastion.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function()
local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike =
CombatLogGetCurrentEventInfo() CombatLogGetCurrentEventInfo()
@ -973,4 +1020,186 @@ function Unit:IsDry()
return not ObjectIsSubmerged(self.unit) return not ObjectIsSubmerged(self.unit)
end end
-- The unit stagger amount
---@return number
function Unit:GetStagger()
return UnitStagger(self:GetOMToken())
end
-- The percent of health the unit is currently staggering
---@return number
function Unit:GetStaggerPercent()
local stagger = self:GetStagger()
local max_health = self:GetMaxHealth()
return (stagger / max_health) * 100
end
-- Get the units power regen rate
---@return number
function Unit:GetPowerRegen()
return GetPowerRegen(self:GetOMToken())
end
-- Get the units staggered health relation
---@return number
function Unit:GetStaggeredHealth()
local stagger = self:GetStagger()
local max_health = self:GetMaxHealth()
return (stagger / max_health) * 100
end
-- get the units combat reach
---@return number
function Unit:GetCombatReach()
return ObjectCombatReach(self:GetOMToken())
end
-- Get the units combat distance (distance - combat reach (realized distance))
---@return number
function Unit:GetCombatDistance(Target)
return self:GetDistance(Target) - Target:GetCombatReach()
end
-- Is the unit within distance of the target (combat reach + distance)
--- If the target is within 8 combat yards (8 + combat reach) of the unit
---@param Target Unit
---@param Distance number
---@return boolean
function Unit:IsWithinCombatDistance(Target, Distance)
if not Target:Exists() then
return false
end
return self:GetDistance(Target) <= Distance + Target:GetCombatReach()
end
-- Check if the unit is within X yards (consider combat reach)
---@param Target Unit
---@param Distance number
---@return boolean
function Unit:IsWithinDistance(Target, Distance)
return self:GetDistance(Target) <= Distance
end
-- Get the angle between the unit and the target in raidans
---@param Target Unit
---@return number
function Unit:GetAngle(Target)
if not Target:Exists() then
return 0
end
local sp = self:GetPosition()
local tp = Target:GetPosition()
local an = Tinkr.Common.GetAnglesBetweenPositions(sp.x, sp.y, sp.z, tp.x, tp.y, tp.z)
return an
end
function Unit:GetFacing()
return ObjectRotation(self:GetOMToken()) or 0
end
-- Check if target is within a arc around the unit (angle, distance) accounting for a rotation of self
---@param Target Unit
---@param Angle number
---@param Distance number
---@param rotation? number
---@return boolean
function Unit:IsWithinCone(Target, Angle, Distance, rotation)
if not Target:Exists() then
return false
end
local angle = self:GetAngle(Target)
rotation = rotation or self:GetFacing()
local diff = math.abs(angle - rotation)
if diff > math.pi then
diff = math.abs(diff - math.pi * 2)
end
return diff <= Angle and self:GetDistance(Target) <= Distance
end
function Unit:GetEmpoweredStage()
local stage = 0
local _, _, _, startTime, _, _, _, spellID, _, numStages = UnitChannelInfo(self:GetOMToken())
if numStages and numStages > 0 then
startTime = startTime / 1000
local currentTime = GetTime()
local stageDuration = 0
for i = 1, numStages do
stageDuration = stageDuration + GetUnitEmpowerStageDuration((self:GetOMToken()), i - 1) / 1000
if startTime + stageDuration > currentTime then
break
end
stage = i
end
end
return stage
end
-- local empowering = {}
-- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_START", function(...)
-- local unit, unk, id = ...
-- if not unit then return end
-- local guid = ObjectGUID(unit)
-- if not guid then return end
-- empowering[guid] = -1
-- end)
-- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_STOP", function(...)
-- local unit, unk, id = ...
-- if not unit then return end
-- local guid = ObjectGUID(unit)
-- if not guid then return end
-- empowering[guid] = -1
-- end)
-- function Unit:GetUnitEmpowerStage()
-- local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages =
-- UnitChannelInfo(self:GetOMToken());
-- if name and empowering[self:GetGUID()] == -1 then
-- empowering[self:GetGUID()] = numStages
-- end
-- if not name and empowering[self:GetGUID()] then
-- return empowering[self:GetGUID()]
-- end
-- if not name then
-- return empowering[self:GetGUID()]
-- end
-- local getStageDuration = function(stage)
-- if stage == numStages then
-- return GetUnitEmpowerHoldAtMaxTime(self:GetOMToken());
-- else
-- return GetUnitEmpowerStageDuration(self:GetOMToken(), stage - 1);
-- end
-- end
-- local time = GetTime() - (startTime / 1000);
-- local higheststage = 0
-- local sumdur = 0
-- for i = 1, numStages - 1, 1 do
-- local duration = getStageDuration(i) / 1000;
-- sumdur = sumdur + duration
-- if time > sumdur then
-- higheststage = i
-- end
-- end
-- return higheststage
-- end
return Unit return Unit

@ -4,49 +4,6 @@ local ObjectManager = Tinkr.Util.ObjectManager
local Unit = Bastion.Unit local Unit = Bastion.Unit
local prefixes = {
'^player',
'^pet',
'^vehicle',
'^target',
'^focus',
'^mouseover',
'^none',
'^npc',
'^party[1-4]',
'^raid[1-4]?[0-9]',
'^boss[1-5]',
'^arena[1-5]'
}
-- Validate a unit is a valid token
local function Validate(token)
local start, index
local length, offset = string.len(token), 0
for i = 1, #prefixes do
start, index = string.find(token, prefixes[i])
if start then
offset = index + 1
if offset > length then
return true
else
while true do
start, index = string.find(token, 'target', offset, true)
if start then
offset = index + 1
if offset > length then
return true
end
else
return false
end
end
end
end
end
return false
end
-- Create a new UnitManager class -- Create a new UnitManager class
---@class UnitManager ---@class UnitManager
local UnitManager = { local UnitManager = {
@ -108,9 +65,6 @@ function UnitManager:New()
return self return self
end end
function UnitManager:Validate(token)
return Validate(token)
end
-- Get or create a unit -- Get or create a unit
---@param token string ---@param token string
@ -153,7 +107,7 @@ end
-- Set a unit by guid -- Set a unit by guid
---@param unit Unit ---@param unit Unit
---@return Unit ---@return nil
function UnitManager:SetObject(unit) function UnitManager:SetObject(unit)
self.objects[unit:GetGUID()] = unit self.objects[unit:GetGUID()] = unit
end end
@ -224,6 +178,7 @@ function UnitManager:GetNumFriendsWithBuff(spell)
if unit:GetAuras():FindMy(spell):IsUp() then if unit:GetAuras():FindMy(spell):IsUp() then
count = count + 1 count = count + 1
end end
return false
end) end)
return count return count
end end
@ -236,6 +191,7 @@ function UnitManager:GetNumFriendsAlive()
if unit:IsAlive() then if unit:IsAlive() then
count = count + 1 count = count + 1
end end
return false
end) end)
return count return count
end end
@ -255,6 +211,7 @@ function UnitManager:GetFriendWithMostFriends(radius)
if other:IsAlive() and u:GetDistance(other) <= radius then if other:IsAlive() and u:GetDistance(other) <= radius then
c = c + 1 c = c + 1
end end
return false
end) end)
if c > count then if c > count then
unit = u unit = u
@ -264,13 +221,46 @@ function UnitManager:GetFriendWithMostFriends(radius)
if other:IsAlive() and u:GetDistance(other) <= radius then if other:IsAlive() and u:GetDistance(other) <= radius then
table.insert(friends, other) table.insert(friends, other)
end end
return false
end) end)
end end
end end
return false
end) end)
return unit, friends return unit, friends
end end
-- Get the enemy with the most enemies within a given radius
function UnitManager:GetEnemiesWithMostEnemies(radius)
local unit = nil
local count = 0
local enemies = {}
self:EnumEnemies(function(u)
if u:IsAlive() then
local c = 0
self:EnumEnemies(function(other)
if other:IsAlive() and u:GetDistance(other) <= radius then
c = c + 1
end
return false
end)
if c > count then
unit = u
count = c
enemies = {}
self:EnumEnemies(function(other)
if other:IsAlive() and u:GetDistance(other) <= radius then
table.insert(enemies, other)
end
return false
end)
end
end
return false
end)
return unit, enemies
end
-- Find the centroid of the most dense area of friends (party/raid members) of a given radius within a given range -- Find the centroid of the most dense area of friends (party/raid members) of a given radius within a given range
---@param radius number ---@param radius number
---@param range number ---@param range number
@ -310,44 +300,12 @@ function UnitManager:FindFriendsCentroid(radius, range)
return centroid return centroid
end end
-- Get the enemy with the most enemies within a given radius -- Find the centroid of the most dense area of enemies of a given radius within a given range
---@param distanceFromPlayer number
---@param radius number
---@return Unit
---@return table
function UnitManager:GetEnemyWithMostEnemies(distanceFromPlayer, radius)
local unit = nil
local count = 0
local enemies = {}
self:EnumEnemies(function(u)
if u:IsAlive() and u:IsAffectingCombat() and u:GetDistance(self:Get("player")) <= distanceFromPlayer then
local c = 0
self:EnumEnemies(function(other)
if other:IsAlive() and other:IsAffectingCombat() and u:GetDistance(other) <= radius then
c = c + 1
end
end)
if c > count then
unit = u
count = c
enemies = {}
self:EnumEnemies(function(other)
if other:IsAlive() and other:IsAffectingCombat() and u:GetDistance(other) <= radius then
table.insert(enemies, other)
end
end)
end
end
end)
return unit, enemies
end
-- Find the centroid of the most dense area of friends (party/raid members) of a given radius within a given range
---@param distanceFromPlayer number
---@param radius number ---@param radius number
---@param range number
---@return Vector3 | nil ---@return Vector3 | nil
function UnitManager:FindEnemiesCentroid(distanceFromPlayer, radius) function UnitManager:FindEnemiesCentroid(radius, range)
local unit, enemies = self:GetEnemyWithMostEnemies(distanceFromPlayer, radius) local unit, enemies = self:GetEnemiesWithMostEnemies(radius)
if unit == nil then if unit == nil then
return nil return nil
end end
@ -362,7 +320,7 @@ function UnitManager:FindEnemiesCentroid(distanceFromPlayer, radius)
centroid = centroid / #enemies centroid = centroid / #enemies
if unit:GetPosition():Distance(centroid) > distanceFromPlayer then if unit:GetPosition():Distance(centroid) > range then
return unit:GetPosition() return unit:GetPosition()
end end
@ -382,4 +340,3 @@ function UnitManager:FindEnemiesCentroid(distanceFromPlayer, radius)
end end
return UnitManager return UnitManager

@ -1,7 +1,7 @@
-- Create a Vector3 class -- Create a Vector3 class
---@class Vector3 ---@class Vector3
local Vector3 = {} local Vector3 = { }
Vector3.__index = Vector3 Vector3.__index = Vector3
---@return string ---@return string
@ -69,87 +69,129 @@ function Vector3:__index(k)
return Vector3[k] return Vector3[k]
end end
---@class Vector3
---@field length number
if k == "length" then if k == "length" then
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
end end
---@class Vector3
---@field normalized Vector3
if k == "normalized" then if k == "normalized" then
local length = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) local length = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
return Vector3:New(self.x / length, self.y / length, self.z / length) return Vector3:New(self.x / length, self.y / length, self.z / length)
end end
---@class Vector3
---@field magnitude number
if k == "magnitude" then if k == "magnitude" then
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
end end
---@class Vector3
---@field sqrMagnitude number
if k == "sqrMagnitude" then if k == "sqrMagnitude" then
return self.x * self.x + self.y * self.y + self.z * self.z return self.x * self.x + self.y * self.y + self.z * self.z
end end
---@class Vector3
---@field zero Vector3
if k == "zero" then if k == "zero" then
return Vector3:New(0, 0, 0) return Vector3:New(0, 0, 0)
end end
---@class Vector3
---@field one Vector3
if k == "one" then if k == "one" then
return Vector3:New(1, 1, 1) return Vector3:New(1, 1, 1)
end end
---@class Vector3
---@field up Vector3
if k == "up" then if k == "up" then
return Vector3:New(0, 1, 0) return Vector3:New(0, 1, 0)
end end
---@class Vector3
---@field down Vector3
if k == "down" then if k == "down" then
return Vector3:New(0, -1, 0) return Vector3:New(0, -1, 0)
end end
---@class Vector3
---@field left Vector3
if k == "left" then if k == "left" then
return Vector3:New(-1, 0, 0) return Vector3:New(-1, 0, 0)
end end
---@class Vector3
---@field right Vector3
if k == "right" then if k == "right" then
return Vector3:New(1, 0, 0) return Vector3:New(1, 0, 0)
end end
---@class Vector3
---@field forward Vector3
if k == "forward" then if k == "forward" then
return Vector3:New(0, 0, 1) return Vector3:New(0, 0, 1)
end end
---@class Vector3
---@field back Vector3
if k == "back" then if k == "back" then
return Vector3:New(0, 0, -1) return Vector3:New(0, 0, -1)
end end
---@class Vector3
---@field positiveInfinity Vector3
if k == "positiveInfinity" then if k == "positiveInfinity" then
return Vector3:New(math.huge, math.huge, math.huge) return Vector3:New(math.huge, math.huge, math.huge)
end end
---@class Vector3
---@field negativeInfinity Vector3
if k == "negativeInfinity" then if k == "negativeInfinity" then
return Vector3:New(-math.huge, -math.huge, -math.huge) return Vector3:New(-math.huge, -math.huge, -math.huge)
end end
---@class Vector3
---@field nan Vector3
if k == "nan" then if k == "nan" then
return Vector3:New(0 / 0, 0 / 0, 0 / 0) return Vector3:New(0 / 0, 0 / 0, 0 / 0)
end end
---@class Vector3
---@field epsilon number
if k == "epsilon" then if k == "epsilon" then
return 1.401298E-45 return 1.401298E-45
end end
---@class Vector3
---@field maxValue number
if k == "maxValue" then if k == "maxValue" then
return 3.402823E+38 return 3.402823E+38
end end
---@class Vector3
---@field minValue number
if k == "minValue" then if k == "minValue" then
return -3.402823E+38 return -3.402823E+38
end end
---@class Vector3
---@field x number
if k == "x" then if k == "x" then
return self[1] return self[1]
end end
---@class Vector3
---@field y number
if k == "y" then if k == "y" then
return self[2] return self[2]
end end
---@class Vector3
---@field z number
if k == "z" then if k == "z" then
return self[3] return self[3]
end end
@ -272,7 +314,7 @@ function Vector3:ProjectOnPlane(planeNormal)
return self - self:Project(planeNormal) return self - self:Project(planeNormal)
end end
---@param inDirection Vector3 ---@param inNormal Vector3
---@return Vector3 ---@return Vector3
function Vector3:Reflect(inNormal) function Vector3:Reflect(inNormal)
return -2 * inNormal:Dot(self) * inNormal + self return -2 * inNormal:Dot(self) * inNormal + self

@ -1,87 +1,136 @@
local Tinkr = ... local Tinkr = ...
local Evaulator = Tinkr.Evaluator
---@class Bastion ---@class Bastion
local Bastion = { local Bastion = {DebugMode = false}
DebugMode = false
}
Bastion.__index = Bastion Bastion.__index = Bastion
function Bastion:Require(file)
-- If require starts with an @ then we require from the scripts/bastion/scripts folder
if file:sub(1, 1) == '@' then
file = file:sub(2)
-- print('1')
return require('scripts/bastion/scripts/' .. file, Bastion)
elseif file:sub(1, 1) == "~" then
file = file:sub(2)
-- print("2")
return require('scripts/bastion/' .. file, Bastion)
else
-- print("Normal req")
return require(file, Bastion)
end
end
local function Load(dir)
local dir = dir
if dir:sub(1, 1) == '@' then
dir = dir:sub(2)
dir = 'scripts/bastion/scripts/' .. dir
end
if dir:sub(1, 1) == '~' then
dir = dir:sub(2)
dir = 'scripts/bastion/' .. dir
end
local files = ListFiles(dir)
for i = 1, #files do
local file = files[i]
if file:sub(-4) == ".lua" or file:sub(-5) == '.luac' then
Bastion:Require(dir .. file:sub(1, -5))
end
end
end
function Bastion.require(class) function Bastion.require(class)
return Tinkr:require("scripts/bastion/src/" .. class .. "/" .. class, Bastion) -- return require("scripts/bastion/src/" .. class .. "/" .. class, Bastion)
return Bastion:Require("~/src/" .. class .. "/" .. class)
end end
---@type ToggleManager -- fenv for all required files
Bastion.ToggleManager = Bastion.require("ToggleManager") function Bastion.Bootstrap()
---@type ClassMagic
Bastion.ClassMagic = Bastion.require("ClassMagic") Bastion.Globals = {}
---@type List
Bastion.List = Bastion.require("List") ---@type ClassMagic
---@type NotificationsList, Notification Bastion.ClassMagic = Bastion.require("ClassMagic")
Bastion.NotificationsList, Bastion.Notification = Bastion.require("NotificationsList") ---@type List
---@type Vector3 Bastion.List = Bastion.require("List")
Bastion.Vector3 = Bastion.require("Vector3") ---@type Library
---@type Sequencer Bastion.Library = Bastion.require("Library")
Bastion.Sequencer = Bastion.require("Sequencer") ---@type NotificationsList, Notification
---@type Command Bastion.NotificationsList, Bastion.Notification = Bastion.require(
Bastion.Command = Bastion.require("Command") "NotificationsList")
---@type Cache ---@type Vector3
Bastion.Cache = Bastion.require("Cache") Bastion.Vector3 = Bastion.require("Vector3")
---@type Cacheable ---@type Sequencer
Bastion.Cacheable = Bastion.require("Cacheable") Bastion.Sequencer = Bastion.require("Sequencer")
---@type Refreshable ---@type Command
Bastion.Refreshable = Bastion.require("Refreshable") Bastion.Command = Bastion.require("Command")
---@type Unit ---@type Cache
Bastion.Unit = Bastion.require("Unit") Bastion.Cache = Bastion.require("Cache")
---@type Aura ---@type Cacheable
Bastion.Aura = Bastion.require("Aura") Bastion.Cacheable = Bastion.require("Cacheable")
---@type APL, APLActor, APLTrait ---@type Refreshable
Bastion.APL, Bastion.APLActor, Bastion.APLTrait = Bastion.require("APL") Bastion.Refreshable = Bastion.require("Refreshable")
---@type Module ---@type Unit
Bastion.Module = Bastion.require("Module") Bastion.Unit = Bastion.require("Unit")
---@type UnitManager ---@type Aura
Bastion.UnitManager = Bastion.require("UnitManager"):New() Bastion.Aura = Bastion.require("Aura")
---@type ObjectManager ---@type APL, APLActor, APLTrait
Bastion.ObjectManager = Bastion.require("ObjectManager"):New() Bastion.APL, Bastion.APLActor, Bastion.APLTrait = Bastion.require("APL")
---@type EventManager ---@type Module
Bastion.EventManager = Bastion.require("EventManager"):New() Bastion.Module = Bastion.require("Module")
---@type Spell ---@type UnitManager
Bastion.Spell = Bastion.require("Spell") Bastion.UnitManager = Bastion.require("UnitManager"):New()
---@type SpellBook ---@type ObjectManager
Bastion.SpellBook = Bastion.require("SpellBook"):New() Bastion.ObjectManager = Bastion.require("ObjectManager"):New()
---@type Item ---@type EventManager
Bastion.Item = Bastion.require("Item") Bastion.EventManager = Bastion.require("EventManager")
---@type ItemBook Bastion.Globals.EventManager = Bastion.EventManager:New()
Bastion.ItemBook = Bastion.require("ItemBook"):New() ---@type Spell
---@type AuraTable Bastion.Spell = Bastion.require("Spell")
Bastion.AuraTable = Bastion.require("AuraTable") ---@type SpellBook
---@type Class Bastion.SpellBook = Bastion.require("SpellBook")
Bastion.Class = Bastion.require("Class") Bastion.Globals.SpellBook = Bastion.SpellBook:New()
---@type Timer ---@type Item
Bastion.Timer = Bastion.require("Timer") Bastion.Item = Bastion.require("Item")
---@type Timer ---@type ItemBook
Bastion.CombatTimer = Bastion.Timer:New('combat') Bastion.ItemBook = Bastion.require("ItemBook")
---@type ContentUtils Bastion.Globals.ItemBook = Bastion.ItemBook:New()
Bastion.ContentUtils = Bastion.require("ContentUtils"):New() ---@type AuraTable
---@type NotificationsList Bastion.AuraTable = Bastion.require("AuraTable")
Bastion.Notifications = Bastion.NotificationsList:New() ---@type Class
Bastion.Class = Bastion.require("Class")
Bastion.modules = {} ---@type Timer
---@type table<string, { duration: number, requestTime: number }> Bastion.Timer = Bastion.require("Timer")
Bastion.pausedModules = {} ---@type Timer
Bastion.Enabled = false Bastion.CombatTimer = Bastion.Timer:New('combat')
---@type MythicPlusUtils
Bastion.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras) Bastion.MythicPlusUtils = Bastion.require("MythicPlusUtils"):New()
---@type NotificationsList
Bastion.Notifications = Bastion.NotificationsList:New()
local LIBRARIES = {}
local MODULES = {}
Bastion.Enabled = false
Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_AURA',
function(unit, auras)
local u = Bastion.UnitManager[unit] local u = Bastion.UnitManager[unit]
if u then if u then u:GetAuras():OnUpdate(auras) end
u:GetAuras():OnUpdate(auras) end)
end
end)
Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED",
function(...)
local unit, castGUID, spellID = ... local unit, castGUID, spellID = ...
local spell = Bastion.SpellBook:GetIfRegistered(spellID) local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID)
if unit == "player" and spell then if unit == "player" and spell then
spell.lastCastAt = GetTime() spell.lastCastAt = GetTime()
@ -90,134 +139,156 @@ Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...)
spell:GetPostCastFunction()(spell) spell:GetPostCastFunction()(spell)
end end
end end
end) end)
local pguid = UnitGUID("player") local pguid = UnitGUID("player")
local missed = {}
Bastion.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED",
local args = { CombatLogGetCurrentEventInfo() } function()
local args = {CombatLogGetCurrentEventInfo()}
local subEvent = args[2]
local sourceGUID = args[4] local sourceGUID = args[4]
local destGUID = args[8] 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 u = Bastion.UnitManager[sourceGUID]
local u2 = Bastion.UnitManager[destGUID] local u2 = Bastion.UnitManager[destGUID]
local t = GetTime() local t = GetTime()
if u then if u then u:SetLastCombatTime(t) end
u:SetLastCombatTime(t)
end
if u2 then if u2 then
u2:SetLastCombatTime(t) 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
end) end)
Bastion.Ticker = C_Timer.NewTicker(0.1, function() Bastion.Ticker = C_Timer.NewTicker(0.1, function()
if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then
Bastion.CombatTimer:Start() Bastion.CombatTimer:Start()
elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then elseif Bastion.CombatTimer:IsRunning() and
not UnitAffectingCombat("player") then
Bastion.CombatTimer:Reset() Bastion.CombatTimer:Reset()
end end
for k, v in pairs(Bastion.pausedModules) do
if (v.duration + v.requestTime) < GetTime() then
Bastion:Print('Module ', k, ' unpaused.')
Bastion.pausedModules[k] = nil
end
end
Bastion.ToggleManager:Refresh()
if Bastion.Enabled then if Bastion.Enabled then
Bastion.ObjectManager:Refresh() Bastion.ObjectManager:Refresh()
for i = 1, #Bastion.modules do for i = 1, #MODULES do MODULES[i]:Tick() end
if not Bastion.pausedModules[Bastion.modules[i].name] then
Bastion.modules[i]:Tick()
end
end
end end
end) end)
function Bastion:Register(module) function Bastion:Register(module)
table.insert(Bastion.modules, module) table.insert(MODULES, module)
Bastion:Print("Registered", module) Bastion:Print("Registered", module)
end
-- Find a module by name
function Bastion:FindModule(name)
for i = 1, #Bastion.modules do
if Bastion.modules[i].name == name then
return Bastion.modules[i]
end end
-- Find a module by name
function Bastion:FindModule(name)
for i = 1, #MODULES do
if MODULES[i].name == name then return MODULES[i] end
end end
return nil return nil
end end
function Bastion:Print(...) function Bastion:Print(...)
local args = { ... } local args = {...}
local str = "|cFFDF362D[Bastion]|r |cFFFFFFFF" local str = "|cFFDF362D[Bastion]|r |cFFFFFFFF"
for i = 1, #args do for i = 1, #args do str = str .. tostring(args[i]) .. " " end
str = str .. tostring(args[i]) .. " "
end
print(str) print(str)
end
function Bastion:Debug(...)
if not Bastion.DebugMode then
return
end end
local args = { ... }
function Bastion:Debug(...)
if not Bastion.DebugMode then return end
local args = {...}
local str = "|cFFDF6520[Bastion]|r |cFFFFFFFF" local str = "|cFFDF6520[Bastion]|r |cFFFFFFFF"
for i = 1, #args do for i = 1, #args do str = str .. tostring(args[i]) .. " " end
str = str .. tostring(args[i]) .. " "
end
print(str) print(str)
end end
local Command = Bastion.Command:New('bastion') local Command = Bastion.Command:New('bastion')
Command:Register('toggle', 'Toggle bastion on/off', function() Command:Register('toggle', 'Toggle bastion on/off', function()
Bastion.Enabled = not Bastion.Enabled Bastion.Enabled = not Bastion.Enabled
if Bastion.Enabled then if Bastion.Enabled then
Bastion:Print("Enabled") Bastion:Print("Enabled")
else else
Bastion:Print("Disabled") Bastion:Print("Disabled")
end end
end) end)
Command:Register('debug', 'Toggle debug mode on/off', function() Command:Register('debug', 'Toggle debug mode on/off', function()
Bastion.DebugMode = not Bastion.DebugMode Bastion.DebugMode = not Bastion.DebugMode
if Bastion.DebugMode then if Bastion.DebugMode then
Bastion:Print("Debug mode enabled") Bastion:Print("Debug mode enabled")
else else
Bastion:Print("Debug mode disabled") Bastion:Print("Debug mode disabled")
end end
end) end)
Command:Register('dumpspells', 'Dump spells to a file', function() Command:Register('dumpspells', 'Dump spells to a file', function()
local i = 1 local i = 1
local rand = math.random(100000, 999999) local rand = math.random(100000, 999999)
local BOOKTYPE_SPELL = BOOKTYPE_SPELL or (Enum.SpellBookSpellBank.Player and Enum.SpellBookSpellBank.Player or 'spell')
while true do while true do
local spellName, spellSubName = GetSpellBookItemName(i, BOOKTYPE_SPELL) local spellName, spellSubName
if not spellName then
do break end if C_SpellBook.GetSpellBookItemName then
spellName, spellSubName = C_SpellBook.GetSpellBookItemName(i, BOOKTYPE_SPELL)
else
spellName, spellSubName = GetSpellBookItemName(i, BOOKTYPE_SPELL)
end end
if not spellName then do break end end
-- use spellName and spellSubName here -- use spellName and spellSubName here
local spellID = select(7, GetSpellInfo(spellName)) local spellID
if C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(spellName)
spellID = info.spellID
else
spellID = select(7, GetSpellInfo(spellName))
end
if spellID then if spellID then
WriteFile('bastion-' .. UnitClass('player') .. '-' .. rand .. '.lua', spellName = spellName:gsub("[%W%s]", "")
"local " .. spellName .. " = Bastion.SpellBook:GetSpell(" .. spellID .. ")", true) WriteFile('bastion-' .. UnitClass('player') .. '-' .. rand ..
'.lua',
"local " .. spellName ..
" = Bastion.Globals.SpellBook:GetSpell(" ..
spellID .. ")\n", true)
end end
i = i + 1 i = i + 1
end end
end) end)
Command:Register('module', 'Toggle a module on/off', function(args) Command:Register('module', 'Toggle a module on/off', function(args)
local module = Bastion:FindModule(args[2]) local module = Bastion:FindModule(args[2])
if module then if module then
module:Toggle() module:Toggle()
@ -229,34 +300,113 @@ Command:Register('module', 'Toggle a module on/off', function(args)
else else
Bastion:Print("Module not found") Bastion:Print("Module not found")
end end
end) end)
Command:Register('pause', 'Pause a module for X seconds', function (args) Command:Register('mplus', 'Toggle m+ module on/off', function(args)
if Bastion.Enabled then local cmd = args[2]
local duration = args[3] if cmd == 'debuffs' then
local moduleName = args[2] Bastion.MythicPlusUtils:ToggleDebuffLogging()
Bastion:Print("Debuff logging", Bastion.MythicPlusUtils
.debuffLogging and "enabled" or "disabled")
return
end
if cmd == 'casts' then
Bastion.MythicPlusUtils:ToggleCastLogging()
Bastion:Print("Cast logging",
Bastion.MythicPlusUtils.castLogging and "enabled" or
"disabled")
return
end
local RequestedModule = Bastion:FindModule(moduleName) Bastion:Print("[MythicPlusUtils] Unknown command")
Bastion:Print("Available commands:")
Bastion:Print("debuffs")
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)
if RequestedModule and not Bastion.pausedModules[moduleName] then ---@param library Library
Bastion:Print("Pausing module ", moduleName, " for ", duration) function Bastion:RegisterLibrary(library)
Bastion.pausedModules[moduleName] = { duration = duration, requestTime = GetTime() } LIBRARIES[library.name] = library
elseif not Bastion.pausedModules[moduleName] then end
Bastion:Print("Unable to find module ", moduleName)
function Bastion:CheckLibraryDependencies()
for k, v in pairs(LIBRARIES) do
if v.dependencies then
for i = 1, #v.dependencies do
local dep = v.dependencies[i]
if LIBRARIES[dep] then
if LIBRARIES[dep].dependencies then
for j = 1, #LIBRARIES[dep].dependencies do
if LIBRARIES[dep].dependencies[j] == v.name then
Bastion:Print(
"Circular dependency detected between " ..
v.name .. " and " .. dep)
return false
end
end
end
else
Bastion:Print("Library " .. v.name .. " depends on " ..
dep .. " but it's not registered")
return false
end
end
end end
end end
end) return true
end
function Bastion:Import(library)
local lib = self:GetLibrary(library)
if not lib then error("Library " .. library .. " not found") end
return lib:Resolve()
end
if UnitClass('player') == 'Mage' then function Bastion:GetLibrary(name)
if GetSpecialization() == 1 then if not LIBRARIES[name] then
Tinkr:require("scripts/bastion/scripts/arcane", Bastion) error("Library " .. name .. " not found")
Eval('RunMacroText("/bastion module arcane")', 'bastion')
elseif GetSpecialization() == 2 then
Tinkr:require("scripts/bastion/scripts/fire", Bastion)
Eval('RunMacroText("/bastion module fire")', 'bastion')
elseif GetSpecialization() == 3 then
Tinkr:require("scripts/bastion/scripts/frost/frost", Bastion)
Eval('RunMacroText("/bastion module frost")', 'bastion')
end end
local library = LIBRARIES[name]
-- if library.dependencies then
-- for i = 1, #library.dependencies do
-- local dep = library.dependencies[i]
-- if LIBRARIES[dep] then
-- if LIBRARIES[dep].dependencies then
-- for j = 1, #LIBRARIES[dep].dependencies do
-- if LIBRARIES[dep].dependencies[j] == library.name then
-- Bastion:Print("Circular dependency detected between " .. library.name .. " and " .. dep)
-- return false
-- end
-- end
-- end
-- else
-- Bastion:Print("Library " .. v.name .. " depends on " .. dep .. " but it's not registered")
-- return false
-- end
-- end
-- end
return library
end
-- if not Bastion:CheckLibraryDependencies() then
-- return
-- end
Load("@Libraries/")
Load("@Modules/")
Load("@")
end end
Bastion.Bootstrap()

Loading…
Cancel
Save