diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5a2625 --- /dev/null +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index dadad45..1d6afaa 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/src/APL/APL.lua b/src/APL/APL.lua index 209f217..539a76c 100644 --- a/src/APL/APL.lua +++ b/src/APL/APL.lua @@ -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) @@ -54,7 +63,7 @@ end ---@param ... APLTrait ---@return APLActor function APLActor:AddTraits(...) - for _, trait in ipairs({...}) do + for _, trait in ipairs({ ... }) do table.insert(self.traits, trait) end @@ -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() + if actorTable.sequencer:ShouldReset() then + actorTable.sequencer:Reset() end end - if self:GetActor().apl then - if self:GetActor().condition and self:GetActor().condition() then - -- print("Bastion: APL:Execute: Executing sub APL " .. self:GetActor().apl.name) - self:GetActor().apl:Execute() + 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 end 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) + 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().spell:CastableIf(self:GetActor().castableFunc):OnCast(self:GetActor().onCastFunc):Cast( - 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 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 == "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 " .. 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.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 +---@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 diff --git a/src/Aura/Aura.lua b/src/Aura/Aura.lua index 056d488..c062f44 100644 --- a/src/Aura/Aura.lua +++ b/src/Aura/Aura.lua @@ -20,7 +20,7 @@ function Aura:__index(k) end -- Equals ----@param other Aura|Spell +---@param other Aura | Spell ---@return boolean function Aura:__eq(other) if getmetatable(other) == Aura then @@ -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 diff --git a/src/AuraTable/AuraTable.lua b/src/AuraTable/AuraTable.lua index 3874e05..67a1c94 100644 --- a/src/AuraTable/AuraTable.lua +++ b/src/AuraTable/AuraTable.lua @@ -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> function AuraTable:GetUnitAuras() if not self.did then self.did = true @@ -730,13 +732,18 @@ 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 - return true + if spell ~= nil then + return aura:IsDispelableBySpell(spell) + else + return true + end end else self:RemoveInstanceID(aura:GetAuraInstanceID()) @@ -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 diff --git a/src/Cacheable/Cacheable.lua b/src/Cacheable/Cacheable.lua index 3dd0214..4623717 100644 --- a/src/Cacheable/Cacheable.lua +++ b/src/Cacheable/Cacheable.lua @@ -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 diff --git a/src/Class/Class.lua b/src/Class/Class.lua index 69db919..574e3d2 100644 --- a/src/Class/Class.lua +++ b/src/Class/Class.lua @@ -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 diff --git a/src/ClassMagic/ClassMagic.lua b/src/ClassMagic/ClassMagic.lua index 3a9ec61..b255ccd 100644 --- a/src/ClassMagic/ClassMagic.lua +++ b/src/ClassMagic/ClassMagic.lua @@ -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) } diff --git a/src/EventManager/EventManager.lua b/src/EventManager/EventManager.lua index 409a445..f1f64ce 100644 --- a/src/EventManager/EventManager.lua +++ b/src/EventManager/EventManager.lua @@ -1,12 +1,19 @@ +---@type Tinkr, Bastion +local Tinkr, Bastion = ... + -- Create an EventManager class ---@class EventManager +---@field frame Frame +---@field events table +---@field eventHandlers table +---@field wowEventHandlers table +---@field selfCLEUHandlers table 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,16 +56,27 @@ 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 not self.wowEventHandlers[event] then - self.wowEventHandlers[event] = {} - self.frame:RegisterEvent(event) - end + 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[event], handler) + table.insert(self.wowEventHandlers[e], handler) + end + else + if not self.wowEventHandlers[event] then + self.wowEventHandlers[event] = {} + self.frame:RegisterEvent(event) + end + + table.insert(self.wowEventHandlers[event], handler) + end end -- Trigger an event @@ -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 diff --git a/src/Item/Item.lua b/src/Item/Item.lua index 5a68e98..72de71b 100644 --- a/src/Item/Item.lua +++ b/src/Item/Item.lua @@ -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 +---@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 - return false + 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 diff --git a/src/Library/Library.lua b/src/Library/Library.lua index 3d0b2b8..ca99870 100644 --- a/src/Library/Library.lua +++ b/src/Library/Library.lua @@ -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,12 +67,12 @@ 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 - self.resolved = {self:ResolveExport(default), remaining} + self.resolved = { self:ResolveExport(default), remaining } return self.resolved[1], self.resolved[2] 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 diff --git a/src/List/List.lua b/src/List/List.lua index 28ef98e..ac73f8d 100644 --- a/src/List/List.lua +++ b/src/List/List.lua @@ -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 diff --git a/src/MythicPlusUtils/MythicPlusUtils.lua b/src/MythicPlusUtils/MythicPlusUtils.lua index f3222b5..a04b1ce 100644 --- a/src/MythicPlusUtils/MythicPlusUtils.lua +++ b/src/MythicPlusUtils/MythicPlusUtils.lua @@ -4,11 +4,11 @@ local Tinkr, Bastion = ... local MythicPlusUtils = { debuffLogging = false, castLogging = false, - random = '', + random = "", loggedCasts = {}, loggedDebuffs = {}, kickList = {}, - aoeBosses = {} + aoeBosses = {}, } MythicPlusUtils.__index = MythicPlusUtils @@ -64,373 +64,1124 @@ function MythicPlusUtils:New() } self.kickList = { + -- Underrot + [260879] = { -- Blood Bolt + [0] = { + true, + false, + false, + }, + }, + [265089] = { -- Dark Reconstituion + [0] = { + true, + false, + false, + }, + }, + [265091] = { -- Gift of Ghuun + [0] = { + true, + false, + false, + }, + }, + [265433] = { -- Withering Curse + [0] = { + true, + false, + false, + }, + }, + [265487] = { -- Shadow Bolt Volley + [0] = { + true, + false, + false, + }, + }, + [265668] = { -- Wave of Decay (low prio) + [0] = { + true, + false, + false, + }, + }, + [266106] = { -- Sonic Screech + [0] = { + true, + false, + false, + }, + }, + [266201] = { -- Bone Shield + [0] = { + true, + false, + false, + }, + }, + [266209] = { -- WickedFrenzy + [0] = { + true, + false, + false, + }, + }, + [272180] = { -- Void Spit + [0] = { + true, + false, + false, + }, + }, + [272183] = { -- Raise Dead + [0] = { + true, + false, + false, + }, + }, + [278755] = { -- Harrowing Despair + [0] = { + true, + false, + false, + }, + }, + [278961] = { -- Decaying Mind + [0] = { + true, + false, + false, + }, + }, + [413044] = { -- Dark Echoes + [0] = { + true, + false, + false, + }, + }, + [265084] = { -- Blood Bolt + [0] = { + true, + false, + false, + }, + }, + [265377] = { -- Hooked Snare + [0] = { + true, + false, + false, + }, + }, + -- Freehold Start + [256060] = { + [0] = { + true, + false, + false, + }, + }, -- Revitalizing Brew + [263274] = { + [0] = { + true, + false, + false, + }, + }, -- Revitalizing Brew + [257784] = { + [0] = { + true, + false, + false, + }, + }, -- Frost Blast + [257397] = { + [0] = { + true, + false, + false, + }, + }, -- Healing Balm + [281420] = { + [0] = { + true, + false, + false, + }, + }, -- Water Bolt + [257732] = { + [0] = { + true, + false, + false, + }, + }, -- Shattering Blow + [257736] = { + [0] = { + true, + false, + false, + }, + }, -- Thundering Squall + [257899] = { -- Painful Motivation + [0] = { + true, + false, + false, + }, + }, + [274507] = { + [0] = { + true, + false, + false, + }, + }, -- Slippery Suds + [258777] = { + [0] = { + true, + false, + false, + }, + }, -- Sea Spout + -- Freehold End + -- Brakenhide Hollow Start + [374544] = { + [0] = { + true, + false, + false, + }, + }, -- Burst of Decay + [382474] = { + [0] = { + true, + false, + false, + }, + }, -- Decay Surge + [381770] = { + [0] = { + true, + false, + false, + }, + }, -- Gushing Ooze + [373804] = { + [0] = { + true, + false, + false, + }, + }, -- Touch of Decay + [385029] = { + [0] = { + true, + false, + false, + }, + }, -- Screech + [378155] = { + [0] = { + true, + false, + false, + }, + }, -- Earth Bolt low prio + [377950] = { + [0] = { + true, + false, + false, + }, + }, -- Greater Healing Rapids + [367500] = { + [0] = { + true, + false, + false, + }, + }, -- Hideous Cackle + [382249] = { + [0] = { + false, + false, + false, + }, + }, -- Earth Bolt + [384638] = { + [0] = { + true, + false, + false, + }, + }, -- Masters Call + [382712] = { + [0] = { + true, + false, + false, + }, + }, -- GTFO + -- Brakenhide Hollow End + -- Halls of Infusion Start + [375384] = { + [0] = { + true, + false, + false, + }, + }, -- Rumbling Earth + [374563] = { + [0] = { + true, + false, + false, + }, + }, -- Dazzle + [375950] = { + [0] = { + true, + false, + false, + }, + }, -- Ice Shards + [374080] = { + [0] = { + true, + false, + false, + }, + }, -- Blasting Gust + [374045] = { + [0] = { + true, + false, + false, + }, + }, -- Expulse + [374339] = { + [0] = { + true, + false, + false, + }, + }, -- Demo Shout + [374066] = { + [0] = { + true, + false, + false, + }, + }, -- Earth Shield + [395694] = { + [0] = { + true, + false, + false, + }, + }, -- Elemental Focus + [374699] = { + [0] = { + true, + false, + false, + }, + }, -- Cauterize + [374706] = { + [0] = { + true, + false, + false, + }, + }, -- Pyretic Burst + [377341] = { + [0] = { + true, + false, + false, + }, + }, -- Tidal Divergence + [377402] = { + [0] = { + true, + false, + false, + }, + }, -- Aqueous Barrier + [376171] = { + [0] = { + true, + false, + false, + }, + }, -- Refreshing Tides + -- Halls of Infusion End + -- Uldaman Start + [369400] = { + [0] = { + true, + false, + false, + }, + }, -- Earthen Ward + [369602] = { + [0] = { + true, + false, + false, + }, + }, -- Defensive Bulwark + [369675] = { + [0] = { + true, + false, + false, + }, + }, -- ChainLightning + [369674] = { + [0] = { + true, + false, + false, + }, + }, -- StoneSpike + [369823] = { + [0] = { + true, + false, + false, + }, + }, -- SpikedCarapace + [369399] = { + [0] = { + true, + false, + false, + }, + }, -- StoneBolt + [369365] = { + [0] = { + true, + false, + false, + }, + }, -- CurseofStoneKick + [369411] = { + [0] = { + true, + false, + false, + }, + }, -- SonicBurst + [377500] = { + [0] = { + true, + false, + false, + }, + }, -- Hasten + -- Uldaman End + -- Nelt Start + [395427] = { + [0] = { + true, + false, + false, + }, + }, -- Burning Roar + [372615] = { + [0] = { + true, + false, + false, + }, + }, -- Ember Reach + [396925] = { + [0] = { + true, + false, + false, + }, + }, -- Lava Bolt + [378282] = { + [0] = { + true, + false, + false, + }, + }, + [384161] = { + [0] = { + true, + false, + false, + }, + }, + [372223] = { + [0] = { + true, + false, + false, + }, + }, + [383651] = { + [0] = { + true, + false, + false, + }, + }, + -- Nelt End + -- VP Start + [188196] = { + [0] = { + true, + false, + false, + }, + }, -- Lightning Bolt + [88170] = { + [0] = { + true, + false, + false, + }, + }, -- Cloud Burst + [410870] = { + [0] = { + true, + false, + false, + }, + }, -- Cyclone + [87779] = { + [0] = { + true, + false, + false, + }, + }, -- Greater Heal + -- VP End + -- NL Start + [202181] = { + [0] = { + true, + false, + false, + }, + }, -- Stone Gaze + [193585] = { + [0] = { + true, + false, + false, + }, + }, -- Bound + [186269] = { + [0] = { + true, + false, + false, + }, + }, -- Stone Bolt + -- NL End + -- DOTI Start + [415770] = { + [0] = { + true, + false, + false, + }, + }, + [411994] = { + [0] = { + true, + false, + false, + }, + }, + [415435] = { + [0] = { + true, + false, + false, + }, + }, + [415437] = { + [0] = { + true, + false, + false, + }, + }, + [411958] = { + [0] = { + true, + false, + false, + }, + }, + [400165] = { + [0] = { + true, + false, + false, + }, + }, + [412922] = { + [0] = { + true, + false, + false, + }, + }, + [417481] = { + [0] = { + true, + false, + false, + }, + }, + [412378] = { + [0] = { + true, + false, + false, + }, + }, + [412233] = { + [0] = { + true, + false, + false, + }, + }, + [413427] = { + [0] = { + true, + false, + false, + }, + }, + -- DOTI End -- Ruby life pools - [372735] = { -- Techtonic Slam + [372735] = { -- Techtonic Slam [187969] = { - false, true, true -- Kick, Stun, Disorient - } + false, + true, + true, -- Kick, Stun, Disorient + }, }, [384933] = { -- Ice Shield [188067] = { - true, true, true - } + true, + true, + true, + }, }, [372749] = { -- Ice Shield [188067] = { - true, true, true - } + true, + true, + true, + }, }, [372743] = { -- Ice Shield [188067] = { - true, true, true - } + true, + true, + true, + }, }, [371984] = { [188067] = { - true, true, true - } + true, + true, + true, + }, }, [373680] = { [188252] = { - true, false, false - } + true, + false, + false, + }, }, [373688] = { [188252] = { - true, false, false - } + true, + false, + false, + }, }, [385310] = { [195119] = { - true, false, false - } + true, + false, + false, + }, }, [384194] = { [190207] = { - true, true, true - } + true, + true, + true, + }, }, [384197] = { [190207] = { - true, true, true - } + true, + true, + true, + }, }, [373017] = { [189886] = { - true, false, false - } + true, + false, + false, + }, }, [392576] = { [198047] = { - true, false, false - } + true, + false, + false, + }, }, [392451] = { [197985] = { - true, true, false, - } + true, + true, + false, + }, }, [392452] = { [197985] = { - true, true, false, - } + true, + true, + false, + }, }, -- Nokhud [383823] = { [192796] = { - false, true, true - } + false, + true, + true, + }, }, [384492] = { [192794] = { - false, true, true - } + false, + true, + true, + }, }, [384365] = { [192800] = { - true, false, false + true, + false, + false, }, [191847] = { - true, false, false - } + true, + false, + false, + }, }, [386012] = { [194317] = { - true, false, false + true, + false, + false, }, [195265] = { - true, false, false + true, + false, + false, }, [194315] = { - true, false, false + true, + false, + false, }, [194316] = { - true, false, false - } - + true, + false, + false, + }, }, [386028] = { [195696] = { - true, false, false - } + true, + false, + false, + }, }, [386024] = { [194894] = { - true, true, true - } + true, + true, + true, + }, }, [386025] = { [194894] = { - true, true, true - } + true, + true, + true, + }, }, [387629] = { [195876] = { - false, true, true - } + false, + true, + true, + }, }, [387608] = { [195842] = { - false, true, true - } + false, + true, + true, + }, }, [387611] = { [195842] = { - false, true, true - } + false, + true, + true, + }, }, [387440] = { [195878] = { - false, true, true - } + false, + true, + true, + }, }, [373395] = { [199717] = { - true, false, false - } + true, + false, + false, + }, }, [376725] = { [190294] = { - true, true, true + true, + true, + true, }, }, [370764] = { [187160] = { - false, true, true + false, + true, + true, }, [196116] = { - false, true, true + false, + true, + true, }, }, [387564] = { [196102] = { - true, true, true - } + true, + true, + true, + }, }, [375596] = { [196115] = { - true, false, false + true, + false, + false, }, [191164] = { - true, false, false + true, + false, + false, }, - }, [386549] = { [186741] = { - true, true, true - } + true, + true, + true, + }, }, [386546] = { [186741] = { - true, true, true - } + true, + true, + true, + }, }, [389804] = { [187154] = { - true, false, false - } + true, + false, + false, + }, }, [377488] = { [187155] = { - true, true, true - } + true, + true, + true, + }, }, [377105] = { [190510] = { - false, true, true - } + false, + true, + true, + }, }, [373932] = { [190187] = { - true, false, false - } + true, + false, + false, + }, }, -- AA [387910] = { [196200] = { - false, true, true - } + false, + true, + true, + }, }, [387975] = { [196202] = { - true, true, true - } + true, + true, + true, + }, }, [388863] = { [196045] = { - true, true, true - } + true, + true, + true, + }, }, [388392] = { [196044] = { - true, true, true - } + true, + true, + true, + }, }, [396812] = { [196576] = { - true, true, true - } + true, + true, + true, + }, }, [377389] = { [192333] = { - true, false, false - } + true, + false, + false, + }, }, [397888] = { [200126] = { - true, true, true - } + true, + true, + true, + }, }, [397801] = { [56448] = { - true, false, false - } + true, + false, + false, + }, }, [395859] = { [59555] = { - true, true, true - } + true, + true, + true, + }, }, [395872] = { [59546] = { - true, false, false - } + true, + false, + false, + }, }, [396018] = { [59552] = { - true, false, false - } + true, + false, + false, + }, }, [396073] = { [59544] = { - true, true, false - } + true, + true, + false, + }, }, [397899] = { [200131] = { - false, true, true - } + false, + true, + true, + }, }, [397914] = { [200137] = { - true, true, true - } + true, + true, + true, + }, }, -- sbg [152818] = { [75713] = { - true, true, false - } + true, + true, + false, + }, }, [398154] = { [75451] = { - false, true, true - } + false, + true, + true, + }, }, [156776] = { [76446] = { - true, true, true - } + true, + true, + true, + }, }, [156772] = { [77700] = { - true, false, false - } + true, + false, + false, + }, }, [153524] = { [75459] = { - true, true, true - } + true, + true, + true, + }, }, [156718] = { [76104] = { - true, false, false - } + true, + false, + false, + }, }, [225100] = { [104270] = { - true, false, false - } + true, + false, + false, + }, }, [210261] = { [104251] = { - true, true, true - } + true, + true, + true, + }, }, [209027] = { [104246] = { - false, true, true - } + false, + true, + true, + }, }, [212031] = { [105705] = { - false, true, false - } + false, + true, + false, + }, }, [212784] = { [105715] = { - false, true, false - } + false, + true, + false, + }, }, [198585] = { [95842] = { - true, true, true - } + true, + true, + true, + }, }, [198959] = { [96664] = { - true, true, true - } + true, + true, + true, + }, }, [215433] = { [95834] = { - true, true, true - } + true, + true, + true, + }, }, [199210] = { [96640] = { - false, true, true - } + false, + true, + true, + }, }, [199090] = { [96611] = { - false, true, true - } + false, + true, + true, + }, }, [185425] = { [96677] = { - false, true, false - } + false, + true, + false, + }, }, [195696] = { [387125] = { - true, false, false - } - } + true, + false, + false, + }, + }, } - Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras) + Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) if not self.debuffLogging then return end @@ -443,7 +1194,7 @@ function MythicPlusUtils:New() 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', [[ + WriteFile("bastion-MPlusDebuffs-" .. self.random .. ".lua", [[ AuraName: ]] .. aura:GetName() .. [[ AuraID: ]] .. aura:GetSpell():GetID() .. "\n" .. [[ ]], true) @@ -453,7 +1204,7 @@ function MythicPlusUtils:New() end end) - Bastion.Globals.EventManager:RegisterWoWEvent('UNIT_SPELLCAST_START', function(unitTarget, castGUID, spellID) + Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_START", function(unitTarget, castGUID, spellID) if not self.castLogging then return end @@ -466,30 +1217,33 @@ function MythicPlusUtils:New() self.loggedCasts[spellID] = true - WriteFile('bastion-MPlusCasts-' .. self.random .. '.lua', [[ + 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 + 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 + if self.loggedCasts[spellID] then + return + end - local name = GetSpellInfo(spellID) + local name = GetSpellInfo(spellID) - self.loggedCasts[spellID] = true + self.loggedCasts[spellID] = true - WriteFile('bastion-MPlusCasts-' .. self.random .. '.lua', [[ + WriteFile("bastion-MPlusCasts-" .. self.random .. ".lua", [[ CastName: ]] .. name .. [[ CastID: ]] .. spellID .. "\n" .. [[ ]], true) - end) + end + ) return self end @@ -505,7 +1259,7 @@ function MythicPlusUtils:ToggleCastLogging() end ---@param unit Unit ----@param percent number +---@param percent? number ---@return boolean function MythicPlusUtils:CastingCriticalKick(unit, percent) local castingSpell = unit:GetCastingOrChannelingSpell() @@ -517,7 +1271,7 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent) return false end - local npcTraits = kickEntry[unit:GetID()] + local npcTraits = kickEntry[unit:GetID()] or kickEntry[0] if not npcTraits then return false diff --git a/src/ObjectManager/ObjectManager.lua b/src/ObjectManager/ObjectManager.lua index 8e21b53..2544119 100644 --- a/src/ObjectManager/ObjectManager.lua +++ b/src/ObjectManager/ObjectManager.lua @@ -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 @@ -80,7 +81,7 @@ function ObjectManager:Refresh() for _, object in pairs(objects) do self:EnumLists(object) - if ({ [5] = true,[6] = true,[7] = true })[ObjectType(object)] then + if ({ [5] = true, [6] = true, [7] = true })[ObjectType(object)] then local unit = Bastion.UnitManager:GetObject(ObjectGUID(object)) if not unit then unit = Bastion.Unit:New(object) @@ -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 diff --git a/src/Sequencer/Sequencer.lua b/src/Sequencer/Sequencer.lua index be1c567..1a5f96d 100644 --- a/src/Sequencer/Sequencer.lua +++ b/src/Sequencer/Sequencer.lua @@ -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) diff --git a/src/Spell/Spell.lua b/src/Spell/Spell.lua index 824cb60..96fd768 100644 --- a/src/Spell/Spell.lua +++ b/src/Spell/Spell.lua @@ -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,11 +172,11 @@ 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 - if type(condition) == "string" and not self:EvaluateCondition(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 @@ -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 diff --git a/src/SpellBook/SpellBook.lua b/src/SpellBook/SpellBook.lua index 52119b8..d946e2b 100644 --- a/src/SpellBook/SpellBook.lua +++ b/src/SpellBook/SpellBook.lua @@ -2,6 +2,7 @@ local Tinkr, Bastion = ... -- Create a new SpellBook class ---@class SpellBook +---@field spells table local SpellBook = {} SpellBook.__index = SpellBook @@ -27,7 +28,7 @@ end ---@return Spell, ... Spell function SpellBook:GetSpells(...) local spells = {} - for _, id in ipairs({...}) do + for _, id in ipairs({ ... }) do table.insert(spells, self:GetSpell(id)) end @@ -38,7 +39,7 @@ end ---@return List function SpellBook:GetList(...) local spells = {} - for _, id in ipairs({...}) do + for _, id in ipairs({ ... }) do table.insert(spells, self:GetSpell(id)) end diff --git a/src/Unit/Unit.lua b/src/Unit/Unit.lua index d9cd4ef..b424137 100644 --- a/src/Unit/Unit.lua +++ b/src/Unit/Unit.lua @@ -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,17 +56,17 @@ 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) - self.unit = unit - self.cache = Bastion.Cache:New() - self.aura_table = Bastion.AuraTable:New(self) + local self = setmetatable({}, Unit) + self.unit = unit + self.cache = Bastion.Cache:New() + self.aura_table = Bastion.AuraTable:New(self) self.regression_history = {} return self end @@ -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 @@ -1128,7 +1155,7 @@ 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() @@ -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(...) diff --git a/src/UnitManager/UnitManager.lua b/src/UnitManager/UnitManager.lua index b1d257c..cc48142 100644 --- a/src/UnitManager/UnitManager.lua +++ b/src/UnitManager/UnitManager.lua @@ -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 diff --git a/src/_bastion.lua b/src/_bastion.lua index 161bce3..085f50e 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -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 @@ -141,7 +178,7 @@ local pguid = UnitGUID("player") local missed = {} Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() - local args = {CombatLogGetCurrentEventInfo()} + local args = { CombatLogGetCurrentEventInfo() } local subEvent = args[2] local sourceGUID = args[4] @@ -216,7 +253,7 @@ function Bastion:FindModule(name) end function Bastion:Print(...) - local args = {...} + local args = { ... } local str = "|cFFDF362D[Bastion]|r |cFFFFFFFF" for i = 1, #args do str = str .. tostring(args[i]) .. " " @@ -228,7 +265,7 @@ function Bastion:Debug(...) if not Bastion.DebugMode then return end - local args = {...} + local args = { ... } local str = "|cFFDF6520[Bastion]|r |cFFFFFFFF" for i = 1, #args do str = str .. tostring(args[i]) .. " " @@ -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()