God Where to start.....

main
ck 1 year ago
parent b43166be76
commit a1e75d0ed1
  1. 12
      .editorconfig
  2. 8
      .gitignore
  3. 168
      src/APL/APL.lua
  4. 121
      src/Aura/Aura.lua
  5. 43
      src/AuraTable/AuraTable.lua
  6. 9
      src/Cacheable/Cacheable.lua
  7. 8
      src/Class/Class.lua
  8. 13
      src/ClassMagic/ClassMagic.lua
  9. 65
      src/EventManager/EventManager.lua
  10. 61
      src/Item/Item.lua
  11. 24
      src/Library/Library.lua
  12. 7
      src/List/List.lua
  13. 1060
      src/MythicPlusUtils/MythicPlusUtils.lua
  14. 6
      src/ObjectManager/ObjectManager.lua
  15. 11
      src/Sequencer/Sequencer.lua
  16. 95
      src/Spell/Spell.lua
  17. 1
      src/SpellBook/SpellBook.lua
  18. 192
      src/Unit/Unit.lua
  19. 18
      src/UnitManager/UnitManager.lua
  20. 95
      src/_bastion.lua

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*.lua]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

8
.gitignore vendored

@ -6,14 +6,6 @@ DS_Store
!.gitkeep
## ignore all files in 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
.vscode/*

@ -1,11 +1,12 @@
-- Document with emmy lua: https://emmylua.github.io/
-- Create an APL trait for the APL class
---@class APLTrait
---@field cb fun(actor?: APLActor):boolean
---@field lastcall number
local APLTrait = {}
APLTrait.__index = APLTrait
-- Constructor
---@param cb fun():boolean
---@param cb fun(actor?: APLActor):boolean
---@return APLTrait
function APLTrait:New(cb)
local self = setmetatable({}, APLTrait)
@ -17,10 +18,11 @@ function APLTrait:New(cb)
end
-- Evaulate the APL trait
---@param actor? APLActor
---@return boolean
function APLTrait:Evaluate()
function APLTrait:Evaluate(actor)
if GetTime() - self.lastcall > 0.1 then
self.lastresult = self.cb()
self.lastresult = self.cb(actor)
self.lastcall = GetTime()
return self.lastresult
end
@ -34,13 +36,20 @@ function APLTrait:__tostring()
return "Bastion.__APLTrait"
end
---@class APLActorTableBase
---@field type "spell" | "item" | "apl" | "sequencer" | "variable" | "action"
---@alias APLActorTable APLActorSpellTable | APLActorItemTable | APLActorAPLTable | APLActorSequencerTable | APLActorVariableTable | APLActorActionTable
-- Create an APL actor for the APL class
---@class APLActor
---@field actor APLActorTable
---@field traits APLTrait[]
local APLActor = {}
APLActor.__index = APLActor
-- Constructor
---@param actor table
---@param actor APLActorTable
function APLActor:New(actor)
local self = setmetatable({}, APLActor)
@ -62,7 +71,7 @@ function APLActor:AddTraits(...)
end
-- Get the actor
---@return table
---@return APLActorTable
function APLActor:GetActor()
return self.actor
end
@ -71,7 +80,7 @@ end
---@return boolean
function APLActor:Evaluate()
for _, trait in ipairs(self.traits) do
if not trait:Evaluate() then
if not trait:Evaluate(self) then
return false
end
end
@ -81,57 +90,71 @@ end
-- Execute
function APLActor:Execute()
local actorTable = self:GetActor()
-- If the actor is a sequencer we don't want to continue executing the APL if the sequencer is not finished
if self:GetActor().sequencer then
if self:GetActor().condition and self:GetActor().condition() and not self:GetActor().sequencer:Finished() then
self:GetActor().sequencer:Execute()
if actorTable.type == "sequencer" then
---@cast actorTable APLActorSequencerTable
if actorTable.condition and actorTable.condition() and not actorTable.sequencer:Finished() then
actorTable.sequencer:Execute()
return true
end
if not self:GetActor().condition and not self:GetActor().sequencer:Finished() then
self:GetActor().sequencer:Execute()
if not actorTable.condition and not actorTable.sequencer:Finished() then
actorTable.sequencer:Execute()
return true
end
-- Check if the sequencer can be reset and reset it if it can
if self:GetActor().sequencer:ShouldReset() then
self:GetActor().sequencer:Reset()
end
if actorTable.sequencer:ShouldReset() then
actorTable.sequencer:Reset()
end
if self:GetActor().apl then
if self:GetActor().condition and self:GetActor().condition() then
-- print("Bastion: APL:Execute: Executing sub APL " .. self:GetActor().apl.name)
self:GetActor().apl:Execute()
end
if actorTable.type == "apl" then
---@cast actorTable APLActorAPLTable
if actorTable.condition and actorTable.condition() then
-- print("Bastion: APL:Execute: Executing sub APL " .. actorTable.apl.name)
if actorTable.apl:Execute() then
return true
end
if self:GetActor().spell then
if self:GetActor().condition then
-- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().spell:CastableIf(self:GetActor().castableFunc):OnCast(self:GetActor().onCastFunc):Cast(
self:GetActor().target, self:GetActor().condition)
else
-- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().spell:CastableIf(self:GetActor().castableFunc):OnCast(self:GetActor().onCastFunc):Cast(
self:GetActor().target)
end
end
if self:GetActor().item then
if self:GetActor().condition then
-- print("Bastion: APL:Execute: Condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target,
self:GetActor().condition)
if actorTable.type == "spell" then
---@cast actorTable APLActorSpellTable
return actorTable.spell
:CastableIf(actorTable.castableFunc)
:OnCast(actorTable.onCastFunc)
:Cast(actorTable.target, actorTable.condition)
--[[ if actorTable.condition then
-- print("Bastion: APL:Execute: Condition for spell " .. actorTable.spell:GetName())
actorTable.spell
:CastableIf(actorTable.castableFunc)
:OnCast(actorTable.onCastFunc)
:Cast(actorTable.target, actorTable.condition)
else
-- print("Bastion: APL:Execute: No condition for spell " .. self:GetActor().spell:GetName())
self:GetActor().item:UsableIf(self:GetActor().usableFunc):Use(self:GetActor().target)
end
-- print("Bastion: APL:Execute: No condition for spell " .. actorTable.spell:GetName())
actorTable.spell:CastableIf(actorTable.castableFunc):OnCast(actorTable.onCastFunc):Cast(actorTable.target)
end ]]
end
if actorTable.type == "item" then
---@cast actorTable APLActorItemTable
return actorTable.item:UsableIf(actorTable.usableFunc):Use(actorTable.target, actorTable.condition)
--[[ if actorTable.condition and type(actorTable.condition) == "string" then
-- print("Bastion: APL:Execute: Condition for spell " .. actorTable.spell:GetName())
actorTable.item:UsableIf(actorTable.usableFunc):Use(actorTable.target, actorTable.condition)
else
-- print("Bastion: APL:Execute: No condition for spell " .. actorTable.spell:GetName())
actorTable.item:UsableIf(actorTable.usableFunc):Use(actorTable.target)
end ]]
end
if self:GetActor().action then
-- print("Bastion: APL:Execute: Executing action " .. self:GetActor().action)
self:GetActor().cb(self)
if actorTable.type == "action" then
---@cast actorTable APLActorActionTable
-- print("Bastion: APL:Execute: Executing action " .. actorTable.action)
actorTable.cb(self)
end
if self:GetActor().variable then
-- print("Bastion: APL:Execute: Setting variable " .. self:GetActor().variable)
self:GetActor()._apl.variables[self:GetActor().variable] = self:GetActor().cb(self:GetActor()._apl)
if actorTable.type == "variable" then
---@cast actorTable APLActorVariableTable
-- print("Bastion: APL:Execute: Setting variable " .. actorTable.variable)
actorTable._apl.variables[actorTable.variable] = actorTable.cb(actorTable._apl)
end
return false
end
@ -150,6 +173,9 @@ end
-- APL (Attack priority list) class
---@class APL
---@field apl APLActor[]
---@field variables table<string, any>
---@field name string
local APL = {}
APL.__index = APL
@ -180,36 +206,54 @@ function APL:GetVariable(name)
return self.variables[name]
end
---@class APLActorVariableTable : APLActorTableBase
---@field variable string
---@field cb fun(...):any
---@field _apl APL
-- Add variable
---@param name string
---@param cb fun(...):any
---@return APLActor
function APL:AddVariable(name, cb)
local actor = APLActor:New({
type = "variable",
variable = name,
cb = cb,
_apl = self
_apl = self,
})
table.insert(self.apl, actor)
return actor
end
---@class APLActorActionTable : APLActorTableBase
---@field action string
---@field cb fun(...):any
-- Add a manual action to the APL
---@param action string
---@param cb fun(...):any
---@return APLActor
function APL:AddAction(action, cb)
local actor = APLActor:New({
type = "action",
action = action,
cb = cb
cb = cb,
})
table.insert(self.apl, actor)
return actor
end
---@class APLActorSpellTable : APLActorTableBase
---@field spell Spell
---@field condition? string|fun(self: Spell): boolean
---@field castableFunc false | fun(self: Spell): boolean
---@field target Unit | false
---@field onCastFunc false | fun(self: Spell):any
-- Add a spell to the APL
---@param spell Spell
---@param condition? string|fun(...):boolean
---@param condition? string|fun(self: Spell):boolean
---@return APLActor
function APL:AddSpell(spell, condition)
local castableFunc = spell.CastableIfFunc
@ -217,11 +261,12 @@ function APL:AddSpell(spell, condition)
local target = spell:GetTarget()
local actor = APLActor:New({
type = "spell",
spell = spell,
condition = condition,
castableFunc = castableFunc,
target = target,
onCastFunc = onCastFunc
onCastFunc = onCastFunc,
})
table.insert(self.apl, actor)
@ -229,19 +274,26 @@ function APL:AddSpell(spell, condition)
return actor
end
---@class APLActorItemTable : APLActorTableBase
---@field item Item
---@field condition? string | fun(self: Item): boolean
---@field usableFunc false | fun(...): boolean
---@field target Unit | nil
-- Add an item to the APL
---@param item Item
---@param condition? fun(...):boolean
---@param condition? string
---@return APLActor
function APL:AddItem(item, condition)
local usableFunc = item.UsableIfFunc
local target = item:GetTarget()
local actor = APLActor:New({
type = "item",
item = item,
condition = condition,
usableFunc = usableFunc,
target = target
target = target,
})
table.insert(self.apl, actor)
@ -249,6 +301,10 @@ function APL:AddItem(item, condition)
return actor
end
---@class APLActorAPLTable : APLActorTableBase
---@field apl APL
---@field condition? fun(...):boolean
-- Add an APL to the APL (for sub APLs)
---@param apl APL
---@param condition fun(...):boolean
@ -258,8 +314,9 @@ function APL:AddAPL(apl, condition)
error("Bastion: APL:AddAPL: No condition for APL " .. apl.name)
end
local actor = APLActor:New({
type = "apl",
apl = apl,
condition = condition
condition = condition,
})
table.insert(self.apl, actor)
return actor
@ -270,24 +327,31 @@ function APL:Execute()
for _, actor in ipairs(self.apl) do
if actor:HasTraits() then
if actor:Evaluate() and actor:Execute() then
break
return true
--break
end
else
if actor:Execute() then
break
return true
--break
end
end
end
end
---@class APLActorSequencerTable : APLActorTableBase
---@field sequencer Sequencer
---@field condition? fun(...):boolean
-- Add a Sequencer to the APL
---@param sequencer Sequencer
---@param condition fun(...):boolean
---@return APLActor
function APL:AddSequence(sequencer, condition)
local actor = APLActor:New({
type = "sequencer",
sequencer = sequencer,
condition = condition
condition = condition,
})
table.insert(self.apl, actor)
return actor

@ -41,11 +41,12 @@ function Aura:__tostring()
end
-- Constructor
---@param unit Unit
---@param index number
---@param type string
---@param unit? Unit
---@param index? number
---@param type? string
function Aura:New(unit, index, type)
if unit == nil then
if unit == nil or index == nil or type == nil then
---@class Aura
local self = setmetatable({}, Aura)
self.aura = {
name = nil,
@ -63,10 +64,10 @@ function Aura:New(unit, index, type)
castByPlayer = false,
nameplateShowAll = false,
timeMod = 0,
points = {},
index = nil,
type = nil
type = nil,
}
if self.aura.spellId then
@ -75,9 +76,9 @@ function Aura:New(unit, index, type)
return self
end
local name, icon, count, dispelType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId,
canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = UnitAura(unit:GetOMToken(), index, type)
local name, icon, count, dispelType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod, v1, v2, v3, v4 =
UnitAura(unit:GetOMToken(), index, type)
---@class Aura
local self = setmetatable({}, Aura)
self.aura = {
name = name,
@ -96,9 +97,14 @@ function Aura:New(unit, index, type)
nameplateShowAll = nameplateShowAll,
timeMod = timeMod,
auraInstanceID = nil,
points = {
[1] = v1,
[2] = v2,
[3] = v3,
[4] = v4,
},
index = index,
type = type
type = type,
}
if self.aura.spellId then
Bastion.Globals.SpellBook:GetSpell(self.aura.spellId)
@ -106,31 +112,75 @@ function Aura:New(unit, index, type)
return self
end
local foodAndDrinkStrings = {
[5] = "Refreshment",
[1] = MINIMAP_TRACKING_VENDOR_FOOD, -- Food & Drink
[2] = POWER_TYPE_FOOD, -- Food
[3] = EMOTE36_TOKEN, -- DRINK
[4] = TUTORIAL_TITLE12, -- Drink
}
---@type { [number]: boolean }
local cachedFoodAndDrinkIDs = {}
function Aura:IsFoodOrDrink()
if self.aura.spellId and self.aura.spellId > 0 and self:IsValid() and self:IsUp() then
if cachedFoodAndDrinkIDs[self.aura.spellId] then
return cachedFoodAndDrinkIDs[self.aura.spellId]
else
local auraName = self.aura.name
if auraName then
print("Aura Name", auraName)
for i = 1, #foodAndDrinkStrings do
if auraName:upper():find(foodAndDrinkStrings[i]:upper()) then
cachedFoodAndDrinkIDs[self.aura.spellId] = true
return true
end
end
else
print("No Aura Name", self.aura.spellId)
end
end
end
return false
end
-- Constructor
---@param unitAuraInfo UnitAuraInfo
---@param unitAuraInfo AuraData
---@return Aura
function Aura:CreateFromUnitAuraInfo(unitAuraInfo)
---@class Aura
local self = setmetatable({}, Aura)
--[[
applications
charges
isNameplateOnly
isRaid
maxCharges
]]
--
self.aura = {
name = unitAuraInfo.name,
icon = unitAuraInfo.icon,
auraInstanceID = unitAuraInfo.auraInstanceID,
canApplyAura = unitAuraInfo.canApplyAura,
castByPlayer = unitAuraInfo.isFromPlayerOrPlayerPet,
count = unitAuraInfo.applications,
dispelType = unitAuraInfo.dispelName,
dispelType = unitAuraInfo.dispelName or "",
duration = unitAuraInfo.duration,
expirationTime = unitAuraInfo.expirationTime,
source = unitAuraInfo.sourceUnit,
icon = unitAuraInfo.icon,
isBossDebuff = unitAuraInfo.isBossAura,
isStealable = unitAuraInfo.isStealable,
name = unitAuraInfo.name,
nameplateShowAll = unitAuraInfo.nameplateShowAll,
nameplateShowPersonal = unitAuraInfo.nameplateShowPersonal,
points = unitAuraInfo.points,
source = unitAuraInfo.sourceUnit,
spellId = unitAuraInfo.spellId,
canApplyAura = unitAuraInfo.canApplyAura,
isBossDebuff = unitAuraInfo.isBossAura,
castByPlayer = unitAuraInfo.isFromPlayerOrPlayerPet,
nameplateShowAll = unitAuraInfo.nameplateShowAll,
timeMod = unitAuraInfo.timeMod,
auraInstanceID = unitAuraInfo.auraInstanceID,
index = nil,
type = unitAuraInfo.isHarmful and "HARMFUL" or "HELPFUL"
type = unitAuraInfo.isHarmful and "HARMFUL" or "HELPFUL",
}
-- Register spell in spellbook
@ -175,7 +225,7 @@ function Aura:GetName()
end
-- Get the auras icon
---@return string
---@return number
function Aura:GetIcon()
return self.aura.icon
end
@ -198,6 +248,15 @@ function Aura:GetDuration()
return self.aura.duration
end
-- Get the auras refresh status
---@return boolean
function Aura:Refreshable()
if not self:IsUp() then
return true
end
return self:GetRemainingTime() < self:GetDuration() * 0.3
end
-- Get the auras remaining time
---@return number
function Aura:GetRemainingTime()
@ -291,23 +350,31 @@ end
-- Check if the aura is dispelable by a spell
---@param spell Spell
function Aura:IsDispelableBySpell(spell)
if
(self:GetDispelType() == "" or self:GetDispelType() == nil)
and self:GetIsStealable()
and spell:IsEnrageDispel()
then
return true
end
if self:GetDispelType() == nil then
return false
end
if self:GetDispelType() == 'Magic' and spell:IsMagicDispel() then
if self:GetDispelType() == "Magic" and spell:IsMagicDispel() then
return true
end
if self:GetDispelType() == 'Curse' and spell:IsCurseDispel() then
if self:GetDispelType() == "Curse" and spell:IsCurseDispel() then
return true
end
if self:GetDispelType() == 'Poison' and spell:IsPoisonDispel() then
if self:GetDispelType() == "Poison" and spell:IsPoisonDispel() then
return true
end
if self:GetDispelType() == 'Disease' and spell:IsDiseaseDispel() then
if self:GetDispelType() == "Disease" and spell:IsDiseaseDispel() then
return true
end

@ -1,7 +1,9 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create a new AuraTable class
---@class AuraTable
---@field unit Unit
local AuraTable = {}
AuraTable.__index = AuraTable
@ -53,7 +55,7 @@ function AuraTable:OnUpdate(auras)
if updatedAuras and #updatedAuras > 0 then
for i = 1, #updatedAuras do
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
local aura = Bastion.Aura:CreateFromUnitAuraInfo(newAura)
self:AddOrUpdateAuraInstanceID(aura:GetAuraInstanceID(), aura)
@ -100,7 +102,7 @@ function AuraTable:AddOrUpdateAuraInstanceID(instanceID, aura)
self.instanceIDLookup[instanceID] = spellId
if Bastion.UnitManager['player']:IsUnit(aura:GetSource()) then
if Bastion.UnitManager["player"]:IsUnit(aura:GetSource()) then
if not self.playerAuras[spellId] then
self.playerAuras[spellId] = {}
end
@ -120,7 +122,7 @@ end
function AuraTable:GetUnitBuffs()
if Tinkr.classic or Tinkr.era then
for i = 1, 40 do
local aura = Bastion.Aura:New(self.unit, i, 'HELPFUL')
local aura = Bastion.Aura:New(self.unit, i, "HELPFUL")
if not aura:IsValid() then
break
@ -128,7 +130,7 @@ function AuraTable:GetUnitBuffs()
local spellId = aura:GetSpell():GetID()
if Bastion.UnitManager['player']:IsUnit(aura:GetSource()) then
if Bastion.UnitManager["player"]:IsUnit(aura:GetSource()) then
if not self.playerAuras[spellId] then
self.playerAuras[spellId] = {}
end
@ -145,7 +147,7 @@ function AuraTable:GetUnitBuffs()
return
end
AuraUtil_ForEachAura(self.unit:GetOMToken(), 'HELPFUL', nil, function(a)
AuraUtil_ForEachAura(self.unit:GetOMToken(), "HELPFUL", nil, function(a)
local aura = Bastion.Aura:CreateFromUnitAuraInfo(a)
if aura:IsValid() then
@ -159,7 +161,7 @@ end
function AuraTable:GetUnitDebuffs()
if Tinkr.classic or Tinkr.era then
for i = 1, 40 do
local aura = Bastion.Aura:New(self.unit, i, 'HARMFUL')
local aura = Bastion.Aura:New(self.unit, i, "HARMFUL")
if not aura:IsValid() then
break
@ -167,7 +169,7 @@ function AuraTable:GetUnitDebuffs()
local spellId = aura:GetSpell():GetID()
if Bastion.UnitManager['player']:IsUnit(aura:GetSource()) then
if Bastion.UnitManager["player"]:IsUnit(aura:GetSource()) then
if not self.playerAuras[spellId] then
self.playerAuras[spellId] = {}
end
@ -184,7 +186,7 @@ function AuraTable:GetUnitDebuffs()
return
end
AuraUtil_ForEachAura(self.unit:GetOMToken(), 'HARMFUL', nil, function(a)
AuraUtil_ForEachAura(self.unit:GetOMToken(), "HARMFUL", nil, function(a)
local aura = Bastion.Aura:CreateFromUnitAuraInfo(a)
if aura:IsValid() then
@ -208,7 +210,7 @@ function AuraTable:Update()
end
-- Get a units auras
---@return table
---@return table<number, table<number, Aura>>
function AuraTable:GetUnitAuras()
if not self.did then
self.did = true
@ -730,14 +732,19 @@ function AuraTable:FindLeastOfFrom(spells, source)
end
-- Has any stealable aura
---@param spell? Spell
---@return boolean
function AuraTable:HasAnyStealableAura()
function AuraTable:HasAnyStealableAura(spell)
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
if aura:GetIsStealable() then
if spell ~= nil then
return aura:IsDispelableBySpell(spell)
else
return true
end
end
else
self:RemoveInstanceID(aura:GetAuraInstanceID())
end
@ -766,4 +773,20 @@ function AuraTable:HasAnyDispelableAura(spell)
return false
end
function AuraTable:HasAnyFoodOrDrinkAura()
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
if aura:IsFoodOrDrink() then
return true
end
else
self:RemoveInstanceID(aura:GetAuraInstanceID())
end
end
end
return false
end
return AuraTable

@ -2,6 +2,9 @@ local Tinkr, Bastion = ...
-- Define a Cacheable class
---@class Cacheable
---@field cache? Cache
---@field callback? fun():any
---@field value any
local Cacheable = {
cache = nil,
callback = nil,
@ -21,9 +24,9 @@ function Cacheable:__index(k)
error("Cacheable:__index: " .. k .. " does not exist")
end
if not self.cache:IsCached('self') then
if not self.cache:IsCached("self") then
self.value = self.callback()
self.cache:Set('self', self.value, 0.5)
self.cache:Set("self", self.value, 0.5)
end
return self.value[k]
@ -45,7 +48,7 @@ function Cacheable:New(value, cb)
self.value = value
self.callback = cb
self.cache:Set('self', self.value, 0.5)
self.cache:Set("self", self.value, 0.5)
return self
end

@ -36,7 +36,7 @@ function Class:New(locale, name, id)
self.class = {
locale = locale,
name = name,
id = id
id = id,
}
return self
end
@ -59,16 +59,10 @@ function Class:GetID()
return self.class.id
end
---@class ColorMixin
---@field r number
---@field g number
---@field b number
-- Return the classes color
---@return ColorMixin classColor
function Class:GetColor()
return C_ClassColor.GetClassColor(self.class.name)
end
return Class

@ -10,8 +10,8 @@ function ClassMagic:Resolve(Class, key)
return Class[key]
end
if Class['Get' .. key:sub(1, 1):upper() .. key:sub(2)] then
local func = Class['Get' .. key:sub(1, 1):upper() .. key:sub(2)]
if Class["Get" .. key:sub(1, 1):upper() .. key:sub(2)] then
local func = Class["Get" .. key:sub(1, 1):upper() .. key:sub(2)]
-- Call the function and return the result if there's more than one return value return it as a table
local result = { func(self) }
@ -22,9 +22,8 @@ function ClassMagic:Resolve(Class, key)
return result[1]
end
if Class['Get' .. key:upper()] then
local func = Class['Get' .. key:upper()]
if Class["Get" .. key:upper()] then
local func = Class["Get" .. key:upper()]
-- Call the function and return the result if there's more than one return value return it as a table
local result = { func(self) }
@ -35,8 +34,8 @@ function ClassMagic:Resolve(Class, key)
return result[1]
end
if Class['Is' .. key:upper()] then
local func = Class['Is' .. key:upper()]
if Class["Is" .. key:upper()] then
local func = Class["Is" .. key:upper()]
-- Call the function and return the result if there's more than one return value return it as a table
local result = { func(self) }

@ -1,12 +1,19 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create an EventManager class
---@class EventManager
---@field frame Frame
---@field events table<string, { [number]: fun(...) }>
---@field eventHandlers table<string, { [number]: fun(...) }>
---@field wowEventHandlers table<string, { [number]: fun(...) }>
---@field selfCLEUHandlers table<string, { [number]: fun(...) }>
local EventManager = {
events = {},
eventHandlers = {},
wowEventHandlers = {},
frame = nil
frame = nil,
}
EventManager.__index = EventManager
-- Constructor
@ -16,11 +23,12 @@ function EventManager:New()
self.events = {}
self.eventHandlers = {}
self.wowEventHandlers = {}
self.selfCLEUHandlers = {}
-- Frame for wow events
self.frame = CreateFrame("Frame")
self.frame:SetScript('OnEvent', function(f, event, ...)
self.frame:SetScript("OnEvent", function(f, event, ...)
if self.wowEventHandlers[event] then
for _, callback in ipairs(self.wowEventHandlers[event]) do
callback(...)
@ -28,6 +36,10 @@ function EventManager:New()
end
end)
self:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function(event)
self:CLEUHandler(event, CombatLogGetCurrentEventInfo())
end)
return self
end
@ -44,10 +56,20 @@ function EventManager:RegisterEvent(event, handler)
end
-- Register a wow event
---@param event string
---@param event string | string[]
---@param handler fun(...)
---@return nil
function EventManager:RegisterWoWEvent(event, handler)
if type(event) == "table" then
for _, e in ipairs(event) do
if not self.wowEventHandlers[e] then
self.wowEventHandlers[e] = {}
self.frame:RegisterEvent(e)
end
table.insert(self.wowEventHandlers[e], handler)
end
else
if not self.wowEventHandlers[event] then
self.wowEventHandlers[event] = {}
self.frame:RegisterEvent(event)
@ -55,6 +77,7 @@ function EventManager:RegisterWoWEvent(event, handler)
table.insert(self.wowEventHandlers[event], handler)
end
end
-- Trigger an event
---@param event string
@ -68,4 +91,38 @@ function EventManager:TriggerEvent(event, ...)
end
end
---@param subevent string | string[]
---@param handler fun(...)
function EventManager:RegisterSelfCLEUEvent(subevent, handler)
if type(subevent) == "table" then
for _, e in ipairs(subevent) do
if not self.selfCLEUHandlers[e] then
self.selfCLEUHandlers[e] = {}
end
table.insert(self.selfCLEUHandlers[e], handler)
end
else
if not self.selfCLEUHandlers[subevent] then
self.selfCLEUHandlers[subevent] = {}
end
table.insert(self.selfCLEUHandlers[subevent], handler)
end
end
---@param event "COMBAT_LOG_EVENT_UNFILTERED"
---@param timestamp number
---@param subevent string
---@param ... any
function EventManager:CLEUHandler(event, timestamp, subevent, ...)
if self.selfCLEUHandlers[subevent] then
if select(2, ...) == UnitGUID("player") then
for _, handler in pairs(self.selfCLEUHandlers[subevent]) do
handler(timestamp, subevent, ...)
end
end
end
end
return EventManager

@ -1,7 +1,15 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create a new Item class
---@class Item
---@field ItemID number
---@field UsableIfFunc boolean | fun(self:Item):boolean
---@field PreUseFunc boolean | fun(self:Item)
---@field target boolean | Unit
---@field conditions table<string, { func: fun(self:Item):boolean }>
---@field OnUseFunc boolean | fun(self:Item)
---@field spellID number | nil
local Item = {
UsableIfFunc = false,
PreUseFunc = false,
@ -16,6 +24,14 @@ local usableExcludes = {
[18562] = true,
}
---@param itemId number | string
---@return number charges, number maxCharges, number start, number duration
local GetItemCharges = function(itemId)
local spellId = select(2, GetItemSpell(itemId))
local charges, maxCharges, start, duration, chargeModRate = GetSpellCharges(spellId)
return charges, maxCharges, start, duration
end
function Item:__index(k)
local response = Bastion.ClassMagic:Resolve(Item, k)
@ -69,7 +85,7 @@ end
-- Get the Items name
---@return string
function Item:GetName()
return GetItemInfo(self:GetID())
return select(1, GetItemInfo(self:GetID()))
end
-- Get the Items icon
@ -111,11 +127,15 @@ end
-- Use the Item
---@param unit Unit
---@param condition string
---@param condition? string | fun(self:Item):boolean
---@return boolean
function Item:Use(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
end
end
if not self:Usable() then
@ -159,7 +179,6 @@ function Item:GetTimeSinceLastUseAttempt()
end
-- Check if the Item is known
---@return boolean
function Item:IsEquipped()
return IsEquippedItem(self:GetID())
end
@ -180,12 +199,11 @@ end
-- Check if the Item is Usable
---@return boolean
function Item:IsEquippedAndUsable()
return ((self:IsEquippable() and self:IsEquipped()) or
(not self:IsEquippable() and self:IsUsable())) and not self:IsOnCooldown()
return ((self:IsEquippable() and self:IsEquipped()) or (not self:IsEquippable() and self:IsUsable()))
and not self:IsOnCooldown()
end
-- Is equippable
---@return boolean
function Item:IsEquippable()
return IsEquippableItem(self:GetID())
end
@ -236,7 +254,7 @@ end
---@param z number
---@return boolean
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
end
if IsSpellPending() == 64 then
@ -270,7 +288,7 @@ function Item:IsInRange(unit)
local them = Object(unit:GetOMToken())
local tx, ty, tz = ObjectPosition(unit:GetOMToken())
local px, py, pz = ObjectPosition('player')
local px, py, pz = ObjectPosition("player")
if not them then
return false
@ -283,16 +301,13 @@ function Item:IsInRange(unit)
local combatReach = ObjectCombatReach("player")
local themCombatReach = ObjectCombatReach(unit:GetOMToken())
if Bastion.UnitManager['player']:InMelee(unit) and Itemmin == 0 then
if Bastion.UnitManager["player"]:InMelee(unit) and Itemmin == 0 then
return true
end
local distance = FastDistance(px, py, pz, tx, ty, tz)
if Itemmax
and distance >= Itemmin
and distance <= combatReach + themCombatReach + Itemmax
then
if Itemmax and distance >= Itemmin and distance <= combatReach + themCombatReach + Itemmax then
return true
end
@ -300,7 +315,6 @@ function Item:IsInRange(unit)
end
-- Get the last use time
---@return number
function Item:GetLastUseTime()
return Bastion.Globals.SpellBook:GetSpell(self:GetID()):GetLastCastTime()
end
@ -317,7 +331,7 @@ end
-- Get the Items charges
---@return number
function Item:GetCharges()
return GetItemCharges(self:GetID())
return select(1, GetItemCharges(self:GetID()))
end
-- Get the Items charges remaining
@ -333,14 +347,13 @@ end
---@return Item
function Item:Condition(name, func)
self.conditions[name] = {
func = func
func = func,
}
return self
end
-- Get a condition for the Item
---@param name string
---@return function | nil
function Item:GetCondition(name)
local condition = self.conditions[name]
if condition then
@ -383,7 +396,7 @@ function Item:SetTarget(unit)
end
-- Get the Items target
---@return Unit | nil
---@return Unit | boolean
function Item:GetTarget()
return self.target
end
@ -392,7 +405,7 @@ end
---@return boolean
function Item:IsMagicDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
@ -400,7 +413,7 @@ end
---@return boolean
function Item:IsCurseDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
@ -408,16 +421,14 @@ end
---@return boolean
function Item:IsPoisonDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
-- IsDiseaseDispel
---@return boolean
function Item:IsDiseaseDispel()
return ({
})[self:GetID()]
return ({})[self:GetID()]
end
---@param item Item

@ -1,25 +1,28 @@
local Tinkr, Bastion = ...
---@class Library
---@field name string
---@field name string | nil
---@field dependencies table
---@field exports table
---@field resolved table
---@field resolved table | nil
local Library = {
name = nil,
dependencies = {},
exports = {
default = function()
return nil
end
end,
},
resolved = nil
resolved = nil,
}
Library.__index = Library
---@param name string
---@param library table
---@class NewLibrary
---@field name string
---@field exports? { default: fun(self: Library):any }
---@param library NewLibrary
---@return Library
function Library:New(library)
local self = {
@ -28,9 +31,9 @@ function Library:New(library)
exports = library.exports or {
default = function()
return nil
end
end,
},
resolved = nil
resolved = nil,
}
self = setmetatable(self, Library)
@ -39,7 +42,7 @@ function Library:New(library)
end
function Library:ResolveExport(export)
if type(export) == 'function' then
if type(export) == "function" then
return export(self)
end
@ -64,7 +67,7 @@ function Library:Resolve()
local default = self.exports.default
local remaining = {}
for k, v in pairs(self.exports) do
if k ~= 'default' then
if k ~= "default" then
remaining[k] = self:ResolveExport(v)
end
end
@ -101,6 +104,7 @@ function Library:Import(library)
error("Library " .. library .. " does not exist")
end
---@diagnostic disable-next-line: undefined-field
if not table.contains(self.dependencies, library) then
table.insert(self.dependencies, library)
end

@ -109,9 +109,10 @@ function List:filter(callback)
return newList
end
---@param callback fun(result: any, value: any): boolean
---@param initialValue any
---@return boolean
---@generic I
---@param callback fun(result: I, value: I): I, boolean?
---@param initialValue I
---@return I
function List:reduce(callback, initialValue)
local result = initialValue
local done = false

File diff suppressed because it is too large Load Diff

@ -1,3 +1,4 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
---@class ObjectManager
@ -33,7 +34,7 @@ function ObjectManager:RegisterList(name, cb)
self._lists[name] = {
list = Bastion.List:New(),
cb = cb
cb = cb,
}
return self._lists[name].list
@ -89,7 +90,7 @@ function ObjectManager:Refresh()
if unit:GetID() == 120651 then
self.explosives:push(unit)
elseif unit:IsPlayer() and (unit:IsInParty() or unit == Bastion.UnitManager['player']) then
elseif unit:IsPlayer() and (unit:IsInParty() or unit == Bastion.UnitManager["player"]) then
self.friends:push(unit)
elseif unit:IsEnemy() then
self.enemies:push(unit)
@ -104,7 +105,6 @@ end
return ObjectManager
-- -- Register a list of objects that are training dummies
-- local dummies = Bastion.ObjectManager:RegisterList('dummies', function(object)
-- if ObjectType(object) == 5 or ObjectType(object) == 6 then

@ -1,13 +1,16 @@
-- Create a sequencer class that takes a table of actions and executes them in order
---@class Sequencer
---@field resetCondition fun(): boolean
---@field abortCondition fun(): boolean
---@field actions fun(sequencer: Sequencer)[]
---@field resetCondition? fun(): boolean
---@field abortCondition? fun(): boolean
---@field actions SequencerAction[]
local Sequencer = {}
Sequencer.__index = Sequencer
---@alias SequencerAction fun(sequence: Sequencer):boolean
-- Constructor
---@param actions table
---@param actions SequencerAction[]
---@param resetCondition? fun(): boolean
---@return Sequencer
function Sequencer:New(actions, resetCondition)
local self = setmetatable({}, Sequencer)

@ -1,7 +1,17 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create a new Spell class
---@class Spell
---@field spellID number
---@field PostCastFunc fun(self:Spell) | false
---@field OnCastFunc fun(self:Spell) | false
---@field PreCastFunc fun(self:Spell) | false
---@field CastableIfFunc false | fun(self:Spell):boolean
---@field lastCastAt number | false
---@field lastCastAttempt number | false
---@field target Unit | false
---@field damageFormula false | fun(self:Spell):number
local Spell = {
CastableIfFunc = false,
PreCastFunc = false,
@ -11,12 +21,16 @@ local Spell = {
wasLooking = false,
lastCastAt = false,
conditions = {},
buffs = {},
debuffs = {},
target = false,
release_at = false
release_at = false,
damageFormula = false,
damage = 0,
}
local usableExcludes = {
[18562] = true
[18562] = true,
}
function Spell:__index(k)
@ -66,7 +80,7 @@ end
-- Get the spells id
---@return number
function Spell:GetID()
return self.spellID
return self:IsOverridden() and self:OverrideSpellID() or self.spellID
end
-- Add post cast func
@ -80,7 +94,7 @@ end
-- Get the spells name
---@return string
function Spell:GetName()
return GetSpellInfo(self:GetID())
return select(1, GetSpellInfo(self:GetID()))
end
-- Get the spells icon
@ -116,19 +130,16 @@ function Spell:GetFullRechargeTime()
end
-- Return the castable function
---@return fun(self:Spell):boolean
function Spell:GetCastableFunction()
return self.CastableIfFunc
end
-- Return the precast function
---@return fun(self:Spell)
function Spell:GetPreCastFunction()
return self.PreCastFunc
end
-- Get the on cast func
---@return fun(self:Spell)
function Spell:GetOnCastFunction()
return self.OnCastFunc
end
@ -161,7 +172,7 @@ end
-- Cast the spell
---@param unit Unit
---@param condition? string|function
---@param condition? string|fun(self:Spell):boolean
---@return boolean
function Spell:Cast(unit, condition)
if condition then
@ -186,7 +197,7 @@ function Spell:Cast(unit, condition)
-- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast
local u = unit:GetOMToken()
if type(u) == "string" and string.find(u, 'nameplate') then
if type(u) == "string" and string.find(u, "nameplate") then
u = Object(u)
end
@ -203,13 +214,11 @@ function Spell:Cast(unit, condition)
if self:GetOnCastFunction() then
self:GetOnCastFunction()(self)
end
return true
end
-- ForceCast the spell
---@param unit Unit
---@param condition string
---@return boolean
function Spell:ForceCast(unit)
-- Call pre cast function
@ -222,7 +231,7 @@ function Spell:ForceCast(unit)
-- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast
local u = unit:GetOMToken()
if type(u) == "string" and string.find(u, 'nameplate') then
if type(u) == "string" and string.find(u, "nameplate") then
u = Object(u)
end
@ -244,15 +253,22 @@ function Spell:ForceCast(unit)
end
-- Get post cast func
---@return fun(self:Spell)
function Spell:GetPostCastFunction()
return self.PostCastFunc
end
function Spell:OverrideSpellID()
return FindSpellOverrideByID(self.spellID)
end
function Spell:IsOverridden()
return self:OverrideSpellID() ~= self.spellID
end
-- Check if the spell is known
---@return boolean
function Spell:IsKnown()
local isKnown = IsSpellKnown(self:GetID())
local isKnown = IsSpellKnownOrOverridesKnown(self:GetID())
local isPlayerSpell = IsPlayerSpell(self:GetID())
return isKnown or isPlayerSpell
end
@ -260,7 +276,8 @@ end
-- Check if the spell is on cooldown
---@return boolean
function Spell:IsOnCooldown()
return select(2, GetSpellCooldown(self:GetID())) > 0
local spellIDName = self:IsOverridden() and self:GetName() or self:GetID()
return select(2, GetSpellCooldown(spellIDName)) > 0
end
-- Check if the spell is usable
@ -322,9 +339,12 @@ end
---@param z? number
---@return boolean
function Spell:Click(x, y, z)
if type(x) == 'table' then
x, y, z = x.x, x.y, x.z
if type(x) == "table" then
z, y, x = x.z, x.y, x.x
end
---@cast x number
---@cast y number
---@cast z number
if IsSpellPending() == 64 then
MouselookStop()
Click(x, y, z)
@ -376,11 +396,11 @@ function Spell:IsInRange(unit)
return true
end
return Bastion.UnitManager['player']:InMelee(unit)
return Bastion.UnitManager["player"]:InMelee(unit)
end
-- Get the last cast time
---@return number
---@return number | false
function Spell:GetLastCastTime()
return self.lastCastAt
end
@ -406,7 +426,7 @@ end
-- Get the spells charges
---@return number
function Spell:GetCharges()
return GetSpellCharges(self:GetID())
return select(1, GetSpellCharges(self:GetID()))
end
function Spell:GetMaxCharges()
@ -451,14 +471,14 @@ end
---@return Spell
function Spell:Condition(name, func)
self.conditions[name] = {
func = func
func = func,
}
return self
end
-- Get a condition for the spell
---@param name string
---@return function | nil
---@return { func: fun(self: Spell): boolean } | nil
function Spell:GetCondition(name)
local condition = self.conditions[name]
if condition then
@ -501,16 +521,23 @@ function Spell:SetTarget(unit)
end
-- Get the spells target
---@return Unit
function Spell:GetTarget()
return self.target
end
-- IsEnrageDispel
---@return boolean
function Spell:IsEnrageDispel()
return ({
[2908] = true,
})[self:GetID()]
end
-- IsMagicDispel
---@return boolean
function Spell:IsMagicDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
@ -518,7 +545,7 @@ end
---@return boolean
function Spell:IsCurseDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
@ -526,7 +553,7 @@ end
---@return boolean
function Spell:IsPoisonDispel()
return ({
[88423] = true
[88423] = true,
})[self:GetID()]
end
@ -547,7 +574,7 @@ end
---@return number
function Spell:GetCost()
local cost = GetSpellPowerCost(self:GetID())
return cost and cost.cost or 0
return cost and cost[1] and cost[1].cost or 0
end
-- IsFree
@ -556,4 +583,18 @@ function Spell:IsFree()
return self:GetCost() == 0
end
---@param damageFormula fun(self:Spell): number
function Spell:RegisterDamageFormula(damageFormula)
self.damageFormula = damageFormula
end
---@return number
function Spell:Damage()
if self.damageFormula then
return self:damageFormula()
else
return self.damage
end
end
return Spell

@ -2,6 +2,7 @@ local Tinkr, Bastion = ...
-- Create a new SpellBook class
---@class SpellBook
---@field spells table<number, Spell>
local SpellBook = {}
SpellBook.__index = SpellBook

@ -1,12 +1,17 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create a new Unit class
---@class Unit
---@field id boolean | number
---@field ttd_ticker false | cbObject
---@field unit TinkrObjectReference
local Unit = {
---@type Cache
cache = nil,
---@type AuraTable
aura_table = nil,
---@type Unit
---@type UnitId | WowGameObject
unit = nil,
last_shadow_techniques = 0,
swings_since_sht = 0,
@ -15,13 +20,13 @@ local Unit = {
last_combat_time = 0,
ttd_ticker = false,
ttd = 0,
id = false,
id = false, --[[ @asnumber ]]
}
function Unit:__index(k)
local response = Bastion.ClassMagic:Resolve(Unit, k)
if k == 'unit' then
if k == "unit" then
return rawget(self, k)
end
@ -40,7 +45,7 @@ end
---@param other Unit
---@return boolean
function Unit:__eq(other)
return UnitIsUnit(self:GetOMToken(), other.unit)
return UnitIsUnit(self:GetOMToken(), other:GetOMToken())
end
-- tostring
@ -51,11 +56,11 @@ end
---```
---@return string
function Unit:__tostring()
return "Bastion.__Unit(" .. tostring(self:GetOMToken()) .. ")" .. " - " .. (self:GetName() or '')
return "Bastion.__Unit(" .. tostring(self:GetOMToken()) .. ")" .. " - " .. (self:GetName() or "")
end
-- Constructor
---@param unit string
---@param unit TinkrObjectReference
---@return Unit
function Unit:New(unit)
local self = setmetatable({}, Unit)
@ -73,9 +78,8 @@ function Unit:IsValid()
end
-- Check if the unit exists
---@return boolean
function Unit:Exists()
return Object(self:GetOMToken())
return Object(self:GetOMToken()) ~= false
end
-- Get the units token
@ -87,11 +91,10 @@ end
-- Get the units name
---@return string
function Unit:GetName()
return UnitName(self:GetOMToken())
return select(1, UnitName(self:GetOMToken()))
end
-- Get the units GUID
---@return string
function Unit:GetGUID()
return ObjectGUID(self:GetOMToken())
end
@ -139,9 +142,9 @@ function Unit:GetHealthPercent()
end
-- Get the units power type
---@return number
---@return Enum.PowerType
function Unit:GetPowerType()
return UnitPowerType(self:GetOMToken())
return select(1, UnitPowerType(self:GetOMToken()))
end
-- Get the units power
@ -226,7 +229,7 @@ end
-- Is the unit a hostile unit
---@return boolean
function Unit:IsHostile()
return UnitCanAttack(self:GetOMToken(), 'player')
return UnitCanAttack(self:GetOMToken(), "player")
end
-- Is the unit a boss
@ -247,7 +250,7 @@ function Unit:IsBoss()
return false
end
---@return string
---@return UnitId
function Unit:GetOMToken()
if not self.unit then
return "none"
@ -341,24 +344,17 @@ local losFlag = bit.bor(0x1, 0x10, 0x100000)
---@param unit Unit
---@return boolean
function Unit:CanSee(unit)
-- mechagon smoke cloud
-- local mechagonID = 2097
-- local smokecloud = 298602
-- local name, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceID, instanceGroupSize, LfgDungeonID =
-- GetInstanceInfo()
-- otherUnit = otherUnit and otherUnit or "player"
-- if instanceID == 2097 then
-- if (self:debuff(smokecloud, unit) and not self:debuff(smokecloud, otherUnit))
-- or (self:debuff(smokecloud, otherUnit) and not self:debuff(smokecloud, unit))
-- then
-- return false
-- end
-- end
local ax, ay, az = ObjectPosition(self:GetOMToken())
local ah = ObjectHeight(self:GetOMToken())
local attx, atty, attz = GetUnitAttachmentPosition(unit:GetOMToken(), 34)
local attx, atty, attz = GetUnitAttachmentPosition(unit:GetOMToken(), 18)
local alwaysLos = {
[189727] = true, -- Khajin the Unyielding
}
--if alwaysLos[unit:GetID()] then
--return true
--end
if not attx or not ax then
return false
@ -391,12 +387,12 @@ function Unit:IsCasting()
end
function Unit:GetTimeCastIsAt(percent)
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
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())
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId =
UnitChannelInfo(self:GetOMToken())
end
if name and startTimeMS and endTimeMS then
@ -413,12 +409,12 @@ end
-- Get Casting or channeling spell
---@return Spell | nil
function Unit:GetCastingOrChannelingSpell()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
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())
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId =
UnitChannelInfo(self:GetOMToken())
end
if name then
@ -431,12 +427,12 @@ 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())
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())
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId =
UnitChannelInfo(self:GetOMToken())
end
if name then
@ -458,6 +454,10 @@ function Unit:IsCastingOrChanneling()
return self:IsCasting() or self:IsChanneling()
end
function Unit:IsImmobilized()
return bit.band(self:GetMovementFlag(), 0x400) > 0
end
-- Check if the unit can attack the target
---@param unit Unit
---@return boolean
@ -467,12 +467,12 @@ end
---@return number
function Unit:GetChannelOrCastPercentComplete()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
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())
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId =
UnitChannelInfo(self:GetOMToken())
end
if name and startTimeMS and endTimeMS then
@ -488,12 +488,12 @@ end
-- Check if unit is interruptible
---@return boolean
function Unit:IsInterruptible()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken())
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())
name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId =
UnitChannelInfo(self:GetOMToken())
end
if name then
@ -534,13 +534,19 @@ function Unit:GetEnemies(range)
local count = 0
Bastion.UnitManager:EnumEnemies(function(unit)
if not self:IsUnit(unit) and self:IsWithinCombatDistance(unit, range) and unit:IsAlive() and self:CanSee(unit) and
unit:IsEnemy() then
if
not self:IsUnit(unit)
and self:IsWithinCombatDistance(unit, range)
and unit:IsAlive()
and self:CanSee(unit)
and unit:IsEnemy()
then
count = count + 1
end
return false
end)
self.cache:Set("enemies_" .. range, count, .5)
self.cache:Set("enemies_" .. range, count, 0.5)
return count
end
@ -555,13 +561,13 @@ function Unit:GetMeleeAttackers()
local count = 0
Bastion.UnitManager:EnumEnemies(function(unit)
if not self:IsUnit(unit) and unit:IsAlive() and self:CanSee(unit) and
self:InMelee(unit) and unit:IsEnemy() then
if not self:IsUnit(unit) and unit:IsAlive() and self:CanSee(unit) and self:InMelee(unit) and unit:IsEnemy() then
count = count + 1
end
return false
end)
self.cache:Set("melee_attackers", count, .5)
self.cache:Set("melee_attackers", count, 0.5)
return count
end
@ -572,10 +578,16 @@ function Unit:GetPartyHPAround(distance, percent)
local count = 0
Bastion.UnitManager:EnumFriends(function(unit)
if not self:IsUnit(unit) and unit:GetDistance(self) <= distance and unit:IsAlive() and self:CanSee(unit) and
unit:GetHP() <= percent then
if
not self:IsUnit(unit)
and unit:GetDistance(self) <= distance
and unit:IsAlive()
and self:CanSee(unit)
and unit:GetHP() <= percent
then
count = count + 1
end
return false
end)
return count
@ -587,6 +599,10 @@ function Unit:IsMoving()
return GetUnitSpeed(self:GetOMToken()) > 0
end
function Unit:GetMovementFlag()
return ObjectMovementFlag(self:GetOMToken())
end
-- Is moving at all
---@return boolean
function Unit:IsMovingAtAll()
@ -627,15 +643,15 @@ end
---@param unit Unit
---@return boolean
function Unit:IsUnit(unit)
return UnitIsUnit(self:GetOMToken(), unit and unit:GetOMToken() or 'none')
return UnitIsUnit(self:GetOMToken(), unit and unit:GetOMToken() or "none")
end
-- IsTanking
---@param unit Unit
---@return boolean
function Unit:IsTanking(unit)
local isTanking, status, threatpct, rawthreatpct, threatvalue = UnitDetailedThreatSituation(self:GetOMToken(),
unit:GetOMToken())
local isTanking, status, threatpct, rawthreatpct, threatvalue =
UnitDetailedThreatSituation(self:GetOMToken(), unit:GetOMToken())
return isTanking
end
@ -651,6 +667,7 @@ function Unit:IsFacing(unit)
return false
end
---@diagnostic disable-next-line: deprecated
local angle = math.atan2(y2 - y, x2 - x) - rot
angle = math.deg(angle)
angle = angle % 360
@ -672,7 +689,7 @@ function Unit:IsBehind(unit)
if not x or not x2 then
return false
end
---@diagnostic disable-next-line: deprecated
local angle = math.atan2(y2 - y, x2 - x) - rot
angle = math.deg(angle)
angle = angle % 360
@ -692,7 +709,16 @@ end
---@return number
function Unit:GetMeleeBoost()
if IsPlayerSpell(196924) then
if IsPlayerSpell(197524) then
local astralNode = C_Traits.GetNodeInfo(C_ClassTalents.GetActiveConfigID() or 0, 82210)
local currentSpec = select(1, GetSpecializationInfo(GetSpecialization()))
if astralNode then
local currentRank = astralNode.activeRank
if currentRank > 0 then
return ((currentSpec == 103 or currentSpec == 104) and 1 or 3) + (currentRank == 2 and 2 or 0)
end
end
elseif IsPlayerSpell(196924) then
return 3
end
return 0
@ -733,9 +759,11 @@ end
-- Get object id
---@return number
function Unit:GetID()
if self.id then return self.id end
if self.id then
return self.id --[[ @as number ]]
end
self.id = ObjectID(self:GetOMToken())
return self.id
return self.id --[[ @as number ]]
end
-- In party
@ -836,7 +864,7 @@ end
function Unit:TimeToDie()
if self:IsDead() then
self.regression_history = {}
if self.ttd_ticker then
if type(self.ttd_ticker) == "table" then
self.ttd_ticker:Cancel()
self.ttd_ticker = false
end
@ -854,8 +882,7 @@ function Unit:TimeToDie()
-- if the unit has more than 5 million health but there's not enough data to make a prediction we can assume there's roughly 250000 damage per second and estimate the time to die
if #self.regression_history < 5 and self:GetMaxHealth() > 5000000 then
return self:GetMaxHealth() /
250000 -- 250000 is an estimate of the average damage per second a well geared group will average
return self:GetMaxHealth() / 250000 -- 250000 is an estimate of the average damage per second a well geared group will average
end
if self.ttd ~= self.ttd or self.ttd < 0 or self.ttd == math.huge then
@ -933,7 +960,7 @@ function Unit:IsStealthed()
local Shadowmeld = Bastion.Globals.SpellBook:GetSpell(58984)
local Sepsis = Bastion.Globals.SpellBook:GetSpell(328305)
return self:GetAuras():FindAny(Stealth) or self:GetAuras():FindAny(ShadowDance)
return self:GetAuras():FindAny(Stealth):IsUp() or self:GetAuras():FindAny(ShadowDance):IsUp()
end
-- Get unit swing timers
@ -963,7 +990,7 @@ function Unit:WatchForSwings()
local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike =
CombatLogGetCurrentEventInfo()
if sourceGUID == self:GetGUID() then
if sourceGUID == self:GetGUID() and subtype then
if subtype == "SPELL_ENERGIZE" and spellID == 196911 then
self.last_shadow_techniques = GetTime()
self.swings_since_sht = 0
@ -1036,8 +1063,8 @@ function Unit:GetStaggerPercent()
end
-- Get the units power regen rate
---@return number
function Unit:GetPowerRegen()
---@diagnostic disable-next-line: redundant-parameter
return GetPowerRegen(self:GetOMToken())
end
@ -1051,7 +1078,7 @@ function Unit:GetStaggeredHealth()
end
-- get the units combat reach
---@return number
---@return number | false
function Unit:GetCombatReach()
return ObjectCombatReach(self:GetOMToken())
end
@ -1144,6 +1171,27 @@ function Unit:GetEmpoweredStage()
return stage
end
function Unit:IsConnected()
return UnitIsConnected(self:GetOMToken())
end
function Unit:HasIncomingRessurection()
return self:IsDead() and UnitHasIncomingResurrection(self:GetOMToken())
end
function Unit:LootTarget()
return ObjectLootTarget(self:GetOMToken())
end
function Unit:CanLoot()
return ObjectLootable(self:GetOMToken())
end
function Unit:HasTarget()
return ObjectTarget(self:GetOMToken()) ~= false
end
function Unit:Target()
return self:HasTarget() and Bastion.UnitManager:Get(ObjectTarget(self:GetOMToken()):unit())
or Bastion.UnitManager:Get("none")
end
-- local empowering = {}
-- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_START", function(...)

@ -169,6 +169,24 @@ function UnitManager:EnumUnits(cb)
end)
end
-- Get the number of enemies with a debuff
---@param spell Spell
---@param range number
---@return number
function UnitManager:GetNumEnemiesWithDebuff(spell, range)
local count = 0
if range == nil then
range = spell:GetRange()
end
self:EnumEnemies(function(unit)
if unit:GetAuras():FindMy(spell):IsUp() then
count = count + 1
end
return false
end)
return count
end
-- Get the number of friends with a buff (party/raid members)
---@param spell Spell
---@return number

@ -1,50 +1,88 @@
---@type Tinkr
local Tinkr = ...
---@class Bastion
local Bastion = {
DebugMode = false
DebugMode = false,
}
local TinkrScriptsBase = "scripts"
local BastionBase = string.format("%s/%s", TinkrScriptsBase, "bastion")
local BastionScriptsBase = string.format("%s/%s", BastionBase, "scripts")
local ThirdPartyModulesBase = string.format("%s/%s", TinkrScriptsBase, "BastionScripts")
Bastion.__index = Bastion
function Bastion:Require(file)
---@param file string
---@param ... any
---@return any ...
function Bastion:Require(file, ...)
-- If require starts with an @ then we require from the scripts/bastion/scripts folder
if file:sub(1, 1) == '@' then
if file:sub(1, 1) == "@" then
file = file:sub(2)
-- print('1')
return require('scripts/bastion/scripts/' .. file, Bastion)
return require(string.format("%s%s", BastionScriptsBase, file), Bastion, ...)
elseif file:sub(1, 1) == "~" then
file = file:sub(2)
-- print("2")
return require('scripts/bastion/' .. file, Bastion)
return require(string.format("%s%s", BastionBase, file), Bastion, ...)
else
-- print("Normal req")
return require(file, Bastion)
return require(file, Bastion, ...)
end
end
local loadExamples = false
local exampleNames = {
"ExampleDependency.lua",
"ExampleDependencyError.lua",
"ExampleLibrary.lua",
"ExampleModule.lua",
}
local function Load(dir)
local dir = dir
if dir:sub(1, 1) == '@' then
if dir:sub(1, 1) == "@" then
dir = dir:sub(2)
dir = 'scripts/bastion/scripts/' .. dir
dir = string.format("%s/%s", BastionScriptsBase, dir)
end
if dir:sub(1, 1) == '~' then
if dir:sub(1, 1) == "~" then
dir = dir:sub(2)
dir = 'scripts/bastion/' .. dir
dir = string.format("%s/%s", BastionBase, 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
return Bastion:Require(dir .. file:sub(1, -5))
local loadFile = true
if not loadExamples then
for j = 1, #exampleNames do
if file:find(exampleNames[j]) then
loadFile = false
break
end
end
end
if loadFile and (file:sub(-4) == ".lua" or file:sub(-5) == ".luac") then
Bastion:Require(dir .. file:sub(1, -5))
end
end
end
local function LoadThird()
local thirdPartyModulesFolders = ListFolders(ThirdPartyModulesBase)
for i = 1, #thirdPartyModulesFolders do
local currentFolderDir = string.format("%s/%s", ThirdPartyModulesBase, thirdPartyModulesFolders[i])
local loaderFilePath = string.format("%s/%s", currentFolderDir, "loader")
if FileExists(loaderFilePath .. ".lua") or FileExists(loaderFilePath .. ".luac") then
Bastion:Require(loaderFilePath, currentFolderDir)
end
end
end
---@return any ...
function Bastion.require(class)
-- return require("scripts/bastion/src/" .. class .. "/" .. class, Bastion)
return Bastion:Require("~/src/" .. class .. "/" .. class)
@ -104,7 +142,7 @@ Bastion.Class = Bastion.require("Class")
---@type Timer
Bastion.Timer = Bastion.require("Timer")
---@type Timer
Bastion.CombatTimer = Bastion.Timer:New('combat')
Bastion.CombatTimer = Bastion.Timer:New("combat")
---@type MythicPlusUtils
Bastion.MythicPlusUtils = Bastion.require("MythicPlusUtils"):New()
---@type NotificationsList
@ -115,7 +153,7 @@ local MODULES = {}
Bastion.Enabled = false
Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras)
Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras)
local u = Bastion.UnitManager[unit]
if u then
@ -125,7 +163,6 @@ end)
Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...)
local unit, castGUID, spellID = ...
local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID)
if unit == "player" and spell then
@ -236,9 +273,9 @@ function Bastion:Debug(...)
print(str)
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
if Bastion.Enabled then
Bastion:Print("Enabled")
@ -247,7 +284,7 @@ Command:Register('toggle', 'Toggle bastion on/off', function()
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
if Bastion.DebugMode then
Bastion:Print("Debug mode enabled")
@ -256,7 +293,7 @@ Command:Register('debug', 'Toggle debug mode on/off', function()
end
end)
Command:Register('dumpspells', 'Dump spells to a file', function()
Command:Register("dumpspells", "Dump spells to a file", function()
local i = 1
local rand = math.random(100000, 999999)
while true do
@ -272,14 +309,17 @@ Command:Register('dumpspells', 'Dump spells to a file', function()
if spellID then
spellName = spellName:gsub("[%W%s]", "")
WriteFile('bastion-' .. UnitClass('player') .. '-' .. rand .. '.lua',
"local " .. spellName .. " = Bastion.Globals.SpellBook:GetSpell(" .. spellID .. ")\n", true)
WriteFile(
"bastion-" .. UnitClass("player") .. "-" .. rand .. ".lua",
"local " .. spellName .. " = Bastion.Globals.SpellBook:GetSpell(" .. spellID .. ")\n",
true
)
end
i = i + 1
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])
if module then
module:Toggle()
@ -293,15 +333,15 @@ Command:Register('module', 'Toggle a module on/off', function(args)
end
end)
Command:Register('mplus', 'Toggle m+ module on/off', function(args)
Command:Register("mplus", "Toggle m+ module on/off", function(args)
local cmd = args[2]
if cmd == 'debuffs' then
if cmd == "debuffs" then
Bastion.MythicPlusUtils:ToggleDebuffLogging()
Bastion:Print("Debuff logging", Bastion.MythicPlusUtils.debuffLogging and "enabled" or "disabled")
return
end
if cmd == 'casts' then
if cmd == "casts" then
Bastion.MythicPlusUtils:ToggleCastLogging()
Bastion:Print("Cast logging", Bastion.MythicPlusUtils.castLogging and "enabled" or "disabled")
return
@ -313,7 +353,7 @@ Command:Register('mplus', 'Toggle m+ module on/off', function(args)
Bastion:Print("casts")
end)
Command:Register('missed', 'Dump the list of immune kidney shot spells', function()
Command:Register("missed", "Dump the list of immune kidney shot spells", function()
for k, v in pairs(missed) do
Bastion:Print(k)
end
@ -395,3 +435,4 @@ end
Load("@Libraries/")
Load("@Modules/")
Load("@")
LoadThird()

Loading…
Cancel
Save