From fb3f8decb6e11f58c730f00bd2eaec4bd3e8f922 Mon Sep 17 00:00:00 2001 From: ck Date: Sat, 10 Feb 2024 15:27:49 -0600 Subject: [PATCH] Updates to TTD. --- src/APL/APL.lua | 5 +- src/APLActor/APLActor.lua | 13 +- src/EventManager/EventManager.lua | 56 ++-- src/Item/Item.lua | 181 +++++++++-- src/ItemBook/ItemBook.lua | 7 + src/Module/Module.lua | 18 +- src/MythicPlusUtils/MythicPlusUtils.lua | 2 +- src/ObjectManager/ObjectManager.lua | 9 +- src/Spell/Spell.lua | 80 ++++- src/TimeToDie/TimeToDie.lua | 25 +- src/Unit/Unit.lua | 403 ++++++++++++------------ src/UnitManager/UnitManager.lua | 6 +- src/Vector3/Vector3.lua | 2 +- src/_bastion.lua | 35 +- 14 files changed, 541 insertions(+), 301 deletions(-) diff --git a/src/APL/APL.lua b/src/APL/APL.lua index e0a99d1..6f20338 100644 --- a/src/APL/APL.lua +++ b/src/APL/APL.lua @@ -150,12 +150,9 @@ end -- Add an APL to the APL (for sub APLs) ---@param apl Bastion.APL ----@param condition fun(...):boolean +---@param condition? fun(...):boolean ---@return Bastion.APLActor function APL:AddAPL(apl, condition) - if not condition then - error("Bastion: APL:AddAPL: No condition for APL " .. apl.name) - end local actor = Bastion.APLActor:New({ type = "apl", apl = apl, diff --git a/src/APLActor/APLActor.lua b/src/APLActor/APLActor.lua index cdfc7a4..303e862 100644 --- a/src/APLActor/APLActor.lua +++ b/src/APLActor/APLActor.lua @@ -80,12 +80,7 @@ function APLActor:Execute() -- If the actor is a sequencer we don't want to continue executing the APL if the sequencer is not finished if actorTable.type == "sequencer" then ---@cast actorTable Bastion.APL.Actor.Sequencer.Table - if actorTable.condition and actorTable.condition() and not actorTable.sequencer:Finished() then - actorTable.sequencer:Execute() - return true - end - - if not actorTable.condition and not actorTable.sequencer:Finished() then + if (not actorTable.condition or actorTable.condition()) and not actorTable.sequencer:Finished() then actorTable.sequencer:Execute() return true end @@ -97,11 +92,9 @@ function APLActor:Execute() end if actorTable.type == "apl" then ---@cast actorTable Bastion.APL.Actor.APL.Table - if actorTable.condition and actorTable.condition() then + if not actorTable.condition or actorTable.condition() then -- print("Bastion: APL:Execute: Executing sub APL " .. actorTable.apl.name) - if actorTable.apl:Execute() then - return true - end + return actorTable.apl:Execute() end end if actorTable.type == "spell" then diff --git a/src/EventManager/EventManager.lua b/src/EventManager/EventManager.lua index a35a4c1..f319bdf 100644 --- a/src/EventManager/EventManager.lua +++ b/src/EventManager/EventManager.lua @@ -6,9 +6,10 @@ local Tinkr, Bastion = ... ---@field frame Frame ---@field events table ---@field eventHandlers table ----@field wowEventHandlers table +---@field wowEventHandlers table ---@field selfCombatEventHandlers table ---@field CombatEventHandlers table +---@field playerGUID string|boolean local EventManager = { events = {}, eventHandlers = {}, @@ -27,6 +28,7 @@ function EventManager:New() self.wowEventHandlers = {} self.selfCombatEventHandlers = {} self.CombatEventHandlers = {} + self.playerGUID = UnitGUID("player") or false -- Frame for wow events self.frame = CreateFrame("Frame") @@ -59,20 +61,14 @@ function EventManager:RegisterEvent(event, handler) end -- Register a wow event ----@param event string | string[] +---@param events WowEvent | WowEvent[] ---@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 +function EventManager:RegisterWoWEvent(events, handler) + if type(events) == "string" then + events = { events } + end - table.insert(self.wowEventHandlers[e], handler) - end - else + for _, event in ipairs(events) do if not self.wowEventHandlers[event] then self.wowEventHandlers[event] = {} self.frame:RegisterEvent(event) @@ -94,33 +90,33 @@ function EventManager:TriggerEvent(event, ...) end end ----@param subevent string | string[] +---@param subevents string | string[] ---@param handler fun(...) -function EventManager:RegisterSelfCombatEvent(subevent, handler) - if type(subevent) == "string" then - subevent = { subevent } +function EventManager:RegisterSelfCombatEvent(subevents, handler) + if type(subevents) == "string" then + subevents = { subevents } end - for _, e in ipairs(subevent) do - if not self.selfCombatEventHandlers[e] then - self.selfCombatEventHandlers[e] = {} + for _, subevent in ipairs(subevents) do + if not self.selfCombatEventHandlers[subevent] then + self.selfCombatEventHandlers[subevent] = {} end - table.insert(self.selfCombatEventHandlers[e], handler) + table.insert(self.selfCombatEventHandlers[subevent], handler) end end ----@param subevent string | string[] +---@param subevents string | string[] ---@param handler fun(...) -function EventManager:RegisterCombatEvent(subevent, handler) - if type(subevent) == "string" then - subevent = { subevent } +function EventManager:RegisterCombatEvent(subevents, handler) + if type(subevents) == "string" then + subevents = { subevents } end - for _, e in ipairs(subevent) do - if not self.CombatEventHandlers[e] then - self.CombatEventHandlers[e] = {} + for _, subevent in ipairs(subevents) do + if not self.CombatEventHandlers[subevent] then + self.CombatEventHandlers[subevent] = {} end - table.insert(self.CombatEventHandlers[e], handler) + table.insert(self.CombatEventHandlers[subevent], handler) end end @@ -128,7 +124,7 @@ end ---@param subevent string ---@param ... any function EventManager:CLEUHandler(timestamp, subevent, ...) - if self.selfCombatEventHandlers[subevent] and select(2, ...) == UnitGUID("player") then + if self.selfCombatEventHandlers[subevent] and self.playerGUID and select(2, ...) == self.playerGUID then for _, callback in ipairs(self.selfCombatEventHandlers[subevent]) do callback(timestamp, subevent, ...) end diff --git a/src/Item/Item.lua b/src/Item/Item.lua index e497dc4..96df453 100644 --- a/src/Item/Item.lua +++ b/src/Item/Item.lua @@ -1,23 +1,37 @@ ---@type Tinkr, Bastion local Tinkr, Bastion = ... +---@class Bastion.Item.Traits.Use +---@field moving? boolean +---@field dead? boolean +---@field casting? boolean +---@field channeling? boolean +---@field byId? boolean + +---@class Bastion.Item.Traits.Target +---@field exists? boolean +---@field player? boolean + -- Create a new Item class ---@class Bastion.Item ----@field ItemID number +---@field itemID number ---@field UsableIfFunc boolean | fun(self:Bastion.Item):boolean ---@field PreUseFunc boolean | fun(self:Bastion.Item) ----@field target boolean | Bastion.Unit +---@field target Bastion.Unit | false ---@field conditions table ---@field OnUseFunc boolean | fun(self:Bastion.Item) ---@field spellID number | nil +---@field lastUpdated boolean|number +---@field playerUsable boolean +---@field shouldUpdate boolean local Item = { UsableIfFunc = false, PreUseFunc = false, OnUseFunc = false, wasLooking = false, + lastUpdated = false, lastUseAttempt = 0, conditions = {}, - target = false, } local usableExcludes = { @@ -27,14 +41,13 @@ local usableExcludes = { ---@param itemId number | string ---@return number charges, number maxCharges, number start, number duration local GetItemCharges = function(itemId) - local spellId = select(2, GetItemSpell(itemId)) + local _, spellId = 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) - if response == nil then response = rawget(self, k) end @@ -62,24 +75,113 @@ end -- Constructor ---@param id number function Item:New(id) + ---@class Bastion.Item local self = setmetatable({}, Item) + self.itemID = id + self.spell = false + self.playerUsable = C_PlayerInfo.CanUseItem(id) + self.shouldUpdate = false + self.traits = { + use = { + moving = true, + dead = false, + casting = false, + channeling = false, + byId = false, + }, + target = { + exists = true, + player = false, + } + } + + -- C_PlayerInfo.CanUseItem + + if not C_Item.IsItemDataCachedByID(id) then + self.shouldUpdate = true + C_Item.RequestLoadItemDataByID(id) + else + local name, spellID = GetItemSpell(self:GetID()) + if spellID then + self.spellID = spellID + Bastion.Globals.SpellBook:GetSpell(spellID) + end + end - self.ItemID = id + return self +end - -- Register spell in spellbook - local name, spellID = GetItemSpell(self:GetID()) - if spellID then - self.spellID = spellID - Bastion.Globals.SpellBook:GetSpell(spellID) +function Item:ShouldUpdate() + if self.shouldUpdate and not self.lastUpdated or GetTime() - self.lastUpdated > 30 then + return true end +end +---@class Bastion.Item.Traits.Params +---@field use? Bastion.Item.Traits.Use +---@field target? Bastion.Item.Traits.Target + +---@param traits Bastion.Item.Traits.Params +function Item:SetTraits(traits) + for _, itemTrait in pairs({ "use", "target" }) do + if type(traits[itemTrait]) == "table" then + local currentTrait = traits[itemTrait] + for traitKey, traitValue in pairs(self.traits[itemTrait]) do + if type(currentTrait[traitKey]) ~= "nil" then + self.traits[itemTrait][traitKey] = currentTrait[traitKey] + end + end + end + end return self end +function Item:EvaluateTraits() + local player = Bastion.UnitManager:Get("player") + + if not self.traits.use.moving and player:IsMoving() then + return false + end + + if not self.traits.use.dead and player:IsDead() then + return false + end + + if not self.traits.use.casting and player:IsCasting() then + return false + end + + if not self.traits.use.channeling and player:IsChanneling() then + return false + end + + if self.traits.target.exists then + local target = self:GetTarget() + if type(target) ~= "boolean" and not target:Exists() or target == false then + return false + end + end + + return true +end + +function Item:Update() + if self:ShouldUpdate() then + if not self.spellID and self.playerUsable then + local name, spellID = GetItemSpell(self:GetID()) + if spellID then + self.spellID = spellID + self.shouldUpdate = false + Bastion.Globals.SpellBook:GetSpell(spellID) + end + end + end +end + -- Get the Items id ---@return number function Item:GetID() - return self.ItemID + return self.itemID end -- Get the Items name @@ -126,7 +228,17 @@ function Item:GetCooldownRemaining() end -- Use the Item ----@param unit Bastion.Unit +---@param unit? Bastion.Unit +function Item:UseByID(unit) + local target = unit and unit:GetOMToken() or self.traits.target.player and "player" or "target" + + RunMacroText(string.format("/use [@%s] item:%d", target, self:GetID())) + + Bastion:Debug("Using by id", self) +end + +-- Use the Item +---@param unit? Bastion.Unit ---@param condition? string | fun(self:Bastion.Item):boolean ---@return boolean function Item:Use(unit, condition) @@ -147,12 +259,14 @@ function Item:Use(unit, condition) self:GetPreUseFunction()(self) end + local target = unit or self.traits.target.player and Bastion.UnitManager:Get("player") or + self.traits.target.exists and self:TargetExists() and self:GetTarget() or false + if not target then + return false + end -- Check if the mouse was looking self.wasLooking = IsMouselooking() - - -- Use the Item - UseItemByName(self:GetName(), unit:GetOMToken()) - + UseItemByName(self.traits.use.byId and self:GetID() or self:GetName(), target:GetOMToken()) Bastion:Debug("Using", self) -- Set the last Use time @@ -186,7 +300,8 @@ end -- Check if the Item is on cooldown ---@return boolean function Item:IsOnCooldown() - return select(2, C_Container.GetItemCooldown(self:GetID())) > 0 + local _, duration = C_Container.GetItemCooldown(self:GetID()) + return duration > 0 end -- Check if the Item is usable @@ -211,6 +326,9 @@ end -- Check if the Item is Usable ---@return boolean function Item:Usable() + if not self:EvaluateTraits() then + return false + end if self:GetUsableFunction() then return self:GetUsableFunction()(self) end @@ -283,7 +401,18 @@ end ---@param unit Bastion.Unit ---@return boolean function Item:IsInRange(unit) - local name, rank, icon, UseTime, Itemmin, Itemmax, ItemID = GetItemInfo(self:GetID()) + if not ItemHasRange(self:GetID()) or not self.spellID then + return true + end + + local itemSpell = self:GetSpell() + + if itemSpell then + return itemSpell:IsInRange(unit) + end + return false + + --[[ local name, rank, icon, UseTime, Itemmin, Itemmax, ItemID = GetItemInfo(self:GetID()) local them = Object(unit:GetOMToken()) @@ -311,7 +440,7 @@ function Item:IsInRange(unit) return true end - return false + return false ]] end -- Get the last use time @@ -334,6 +463,10 @@ function Item:GetCharges() return select(1, GetItemCharges(self:GetID())) end +function Item:GetCount() + return GetItemCount(self:GetID(), false, false, false) +end + -- Get the Items charges remaining ---@return number function Item:GetChargesRemaining() @@ -389,16 +522,18 @@ end -- Set the Items target ---@param unit Bastion.Unit ----@return Bastion.Item function Item:SetTarget(unit) self.target = unit return self end -- Get the Items target ----@return Bastion.Unit | boolean function Item:GetTarget() - return self.target + return self.traits.target.player and Bastion.UnitManager:Get("player") or self.target +end + +function Item:TargetExists() + return self:GetTarget() and self:GetTarget():Exists() or false end -- IsMagicDispel diff --git a/src/ItemBook/ItemBook.lua b/src/ItemBook/ItemBook.lua index ab4a7de..fa1e75a 100644 --- a/src/ItemBook/ItemBook.lua +++ b/src/ItemBook/ItemBook.lua @@ -14,6 +14,13 @@ ItemBook.__index = ItemBook function ItemBook:New() local self = setmetatable({}, ItemBook) self.items = {} + ---@param itemId itemId + ---@param success boolean + Bastion.Globals.EventManager:RegisterEvent("ITEM_DATA_LOAD_RESULT", function(itemId, success) + if itemId and success and self.items[itemId] then + self.items[itemId]:Update() + end + end) return self end diff --git a/src/Module/Module.lua b/src/Module/Module.lua index 9e9367f..4be4cb6 100644 --- a/src/Module/Module.lua +++ b/src/Module/Module.lua @@ -1,9 +1,14 @@ +---@type Tinkr, Bastion +local Tinkr, Bastion = ... + -- Create a module class for a bastion module ---@class Bastion.Module ---@field name string ---@field enabled boolean ---@field synced function[] +---@field interval? number +---@field nextTick number local Module = {} Module.__index = Module @@ -15,15 +20,17 @@ end -- Constructor ---@param name string +---@param interval? number ---@return Bastion.Module -function Module:New(name) +function Module:New(name, interval) local module = {} setmetatable(module, Module) module.name = name module.enabled = false module.synced = {} - + module.interval = interval + module.nextTick = 0 return module end @@ -67,8 +74,11 @@ end -- Sync function Module:Tick() if self.enabled then - for i = 1, #self.synced do - self.synced[i]() + if Bastion.Tick >= self.nextTick then + self.nextTick = Bastion.Tick + (self.interval or 1) + for i = 1, #self.synced do + self.synced[i]() + end end end end diff --git a/src/MythicPlusUtils/MythicPlusUtils.lua b/src/MythicPlusUtils/MythicPlusUtils.lua index cc503c1..9ae0466 100644 --- a/src/MythicPlusUtils/MythicPlusUtils.lua +++ b/src/MythicPlusUtils/MythicPlusUtils.lua @@ -1300,7 +1300,7 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent) end ---@param unit Bastion.Unit ----@param percent number +---@param percent? number ---@return boolean function MythicPlusUtils:CastingCriticalStun(unit, percent) local castingSpell = unit:GetCastingOrChannelingSpell() diff --git a/src/ObjectManager/ObjectManager.lua b/src/ObjectManager/ObjectManager.lua index e5a6075..88b34cf 100644 --- a/src/ObjectManager/ObjectManager.lua +++ b/src/ObjectManager/ObjectManager.lua @@ -92,8 +92,8 @@ function ObjectManager:Refresh() for _, object in pairs(objects) do self:EnumLists(object) - - if ({ [5] = true, [6] = true, [7] = true })[ObjectType(object)] then + local objectType = ObjectType(object) + if ({ [5] = true, [6] = true, [7] = true })[objectType] then local objectGUID = ObjectGUID(object) if objectGUID then local unit = Bastion.UnitManager:GetObject(objectGUID) @@ -102,7 +102,8 @@ function ObjectManager:Refresh() Bastion.UnitManager:SetObject(unit) end - if ObjectType(object) == 5 and ObjectCreatureType(object) == 8 then + local creatureType = ObjectCreatureType(object) + if objectType == 5 and creatureType == 8 then self.critters:push(unit) elseif unit:GetID() == 204560 then self.incorporeal:push(unit) @@ -110,7 +111,7 @@ function ObjectManager:Refresh() self.afflicted:push(unit) elseif 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:IsInPartyOrRaid() or unit == Bastion.UnitManager["player"]) then self.friends:push(unit) elseif unit:IsEnemy() then self.enemies:push(unit) diff --git a/src/Spell/Spell.lua b/src/Spell/Spell.lua index dbc52b8..361ba79 100644 --- a/src/Spell/Spell.lua +++ b/src/Spell/Spell.lua @@ -9,6 +9,15 @@ local Tinkr, Bastion = ... ---@field channeling boolean ---@field override boolean ---@field talent boolean | spellId +---@field power boolean + +---@class Bastion.Spell.Traits.Cost +---@field type Enum.PowerType +---@field cost number +---@field minCost number +---@field requiredAuraID number +---@field costPercent number +---@field costPerSecond number ---@class Bastion.Spell.Traits.Target ---@field exists boolean @@ -17,13 +26,16 @@ local Tinkr, Bastion = ... ---@class Bastion.Spell.Traits ---@field cast Bastion.Spell.Traits.Cast ---@field target Bastion.Spell.Traits.Target +---@field cost Bastion.Spell.Traits.Cost[] ---@class Bastion.Spell.Traits.Cast.Params : Bastion.Spell.Traits.Cast, { [string]?: boolean } ---@class Spell.Traits.Target.Params : Bastion.Spell.Traits.Target, { [string]?: boolean } +---@class Spell.Traits.Cost.Params : Bastion.Spell.Traits.Cost[] ---@class Bastion.Spell.Traits.Params ---@field cast? Bastion.Spell.Traits.Cast.Params ---@field target? Spell.Traits.Target.Params +---@field cost? Spell.Traits.Cost.Params ---@class Bastion.Spell.Aura ---@field spell Bastion.Spell @@ -49,7 +61,17 @@ local Tinkr, Bastion = ... ---@field target Bastion.Unit | false ---@field traits Bastion.Spell.Traits ---@field wasLooking boolean -local Spell = {} +local Spell = { + CastableIfFunc = false, + damage = 0, + damageFormula = false, + lastCastAt = false, + lastCastAttempt = false, + OnCastFunc = false, + PostCastFunc = false, + PreCastFunc = false, + release_at = false, +} local usableExcludes = { [18562] = true, @@ -86,16 +108,7 @@ function Spell:New(id) ---@class Bastion.Spell local self = setmetatable({}, Spell) self.auras = {} - self.CastableIfFunc = false - self.damage = 0 - self.damageFormula = false - self.lastCastAt = false - self.lastCastAttempt = false - self.OnCastFunc = false self.overrides = {} - self.PostCastFunc = false - self.PreCastFunc = false - self.release_at = false self.conditions = {} self.spellID = id self.traits = { @@ -107,11 +120,13 @@ function Spell:New(id) channeling = false, override = false, talent = false, + power = false, }, target = { exists = true, player = false, - } + }, + cost = {}, } self.target = false self.wasLooking = false @@ -218,6 +233,10 @@ end ---@param condition? string | fun(self:Bastion.Spell):boolean ---@return boolean function Spell:Cast(unit, condition) + if not self:Castable() then + return false + end + if condition then if type(condition) == "string" and not self:EvaluateCondition(condition) then return false @@ -226,9 +245,6 @@ function Spell:Cast(unit, condition) end end - if not self:Castable() then - return false - end -- Call pre cast function if self:GetPreCastFunction() then @@ -351,12 +367,22 @@ function Spell:SetTraits(traits) end end end + if traits.cost then + self.traits.cost = {} + for _, cost in ipairs(traits.cost) do + table.insert(self.traits.cost, cost) + end + end return self end function Spell:EvaluateTraits() local player = Bastion.UnitManager:Get("player") + if self.traits.cast.power and not self:HasPower() then + return false + end + if not self.traits.cast.global and player:GetGCD() > 0 then return false end @@ -678,6 +704,27 @@ function Spell:IsSpell(spell) return self:GetID() == spell:GetID() end +function Spell:HasPower() + local costs = GetSpellPowerCost(self:GetID()) + local checked = {} + local hasPower = #costs > 0 and false or true + if not hasPower then + for _, cost in ipairs(costs) do + if not checked[cost.type] then + local powerCost = (cost.cost > cost.minCost and cost.minCost or cost.cost) + if cost.hasRequiredAura or cost.requiredAuraID == 0 then + hasPower = powerCost == 0 or Bastion.UnitManager:Get("player"):GetPower(cost.type) >= powerCost + checked[cost.type] = true + if not hasPower then + return false + end + end + end + end + end + return hasPower +end + -- GetCost ---@return number function Spell:GetCost() @@ -705,9 +752,12 @@ function Spell:Damage() end end ----@param target Bastion.Unit +---@param target? Bastion.Unit ---@param source? "any" | Bastion.Unit function Spell:GetAura(target, source) + if type(target) == "nil" then + target = Bastion.UnitManager:Get("player") + end if type(source) == "nil" then source = Bastion.UnitManager:Get("player") end diff --git a/src/TimeToDie/TimeToDie.lua b/src/TimeToDie/TimeToDie.lua index 1f3c093..a61f6d7 100644 --- a/src/TimeToDie/TimeToDie.lua +++ b/src/TimeToDie/TimeToDie.lua @@ -25,7 +25,23 @@ local TimeToDie = { Units = {}, -- Used to track units, ---@type table ExistingUnits = {}, -- Used to track GUIDs of currently existing units (to be compared with tracked units) - Throttle = 0 + Throttle = 0, + ---@enum Bastion.TimeToDie.Enums + Enums = { + --- No GUID + NO_GUID = -1, -- 11111 + --- Negative TTD + NEGATIVE_TTD = -2, -- 9999 + -- Not updated/Not enough samples + NOT_UPDATED = -3, -- 8888 + -- No DPS + NO_DPS = -4, -- 7777 + -- Dummy + DUMMY = -5, -- 6666 + -- Player + PLAYER = -6, -- 25 + DOES_NOT_EXIST = -7, + }, } function TimeToDie:IterableUnits() @@ -216,6 +232,7 @@ TimeToDie.specialTTDPercentageData = { ---@param enemies? Bastion.List ---@param bossOnly? boolean function TimeToDie.FightRemains(enemies, bossOnly) + ---@type boolean, number local bossExists, maxTimeToDie for i = 1, 4 do local bossUnit = Bastion.UnitManager:Get(string.format("boss%d", i)) @@ -229,7 +246,7 @@ function TimeToDie.FightRemains(enemies, bossOnly) if bossExists or bossOnly then -- If we have a boss list but no valid boss time, return invalid - return maxTimeToDie or 11111 + return maxTimeToDie or TimeToDie.Enums.NO_GUID -- 11111 end -- If we specify an AoE range, iterate through all the targets in the specified range @@ -256,7 +273,7 @@ end -- Get if the Time To Die is Valid for a boss fight remains function TimeToDie.BossFightRemainsIsNotValid() - return TimeToDie.BossFightRemains() >= 7777 + return TimeToDie.BossFightRemains() < 0 end -- Returns if the current fight length meets the requirements. @@ -267,7 +284,7 @@ end ---@param bossOnly boolean function TimeToDie.FilteredFightRemains(enemies, operator, value, checkIfValid, bossOnly) local fightRemains = TimeToDie.FightRemains(enemies, bossOnly) - if checkIfValid and fightRemains >= 7777 then + if checkIfValid and fightRemains < 0 then return false end diff --git a/src/Unit/Unit.lua b/src/Unit/Unit.lua index f3f81c9..87f8205 100644 --- a/src/Unit/Unit.lua +++ b/src/Unit/Unit.lua @@ -16,8 +16,23 @@ local Unit = { ttd_ticker = false, ttd = 0, id = false, --[[ @asnumber ]] + watching_for_swings = false, + health = {} } +function Unit:UpdateHealth() + if #self.health > 60 then + table.remove(self.health, 1) + end + table.insert(self.health, { + time = Bastion.Now, + percent = self:GetHP(), + health = self:GetHealth(), + maxHealth = + self:GetMaxHealth() + }) +end + function Unit:__index(k) local response = Bastion.ClassMagic:Resolve(Unit, k) @@ -56,6 +71,7 @@ end -- Constructor ---@param unit? TinkrObjectReference +---@return Bastion.Unit function Unit:New(unit) ---@class Bastion.Unit local self = setmetatable({}, Unit) @@ -68,6 +84,7 @@ function Unit:New(unit) self.cache = Bastion.Cache:New() self.aura_table = Bastion.AuraTable:New(self) self.regression_history = {} + self.health = {} return self end @@ -78,12 +95,12 @@ function Unit:IsValid() end -- Check if the unit exists +---@return boolean function Unit:Exists() return Object(self:GetOMToken()) ~= false end -- Get the units token ----@return string function Unit:Token() return self:GetOMToken() end @@ -180,7 +197,6 @@ function Unit:GetPowerDeficit(powerType) end -- Get the units position ----@return Bastion.Vector3 function Unit:GetPosition() local x, y, z = ObjectPosition(self:GetOMToken()) return Bastion.Vector3:New(x, y, z) @@ -311,11 +327,12 @@ function Unit:IsDamage() end -- Get the units role ----@return "TANK" | "HEALER" | "DAMAGER" +---@return "TANK" | "HEALER" | "DAMAGER" | "NONE" function Unit:GetRole() return UnitGroupRolesAssigned(self:GetOMToken()) end +---@return number | boolean function Unit:GetSpecializationID() if CanInspect(self:GetOMToken(), false) then return ObjectSpecializationID(self:GetOMToken()) @@ -324,10 +341,10 @@ function Unit:GetSpecializationID() end ---@param fallback? boolean ----@return "TANK" | "HEALER" | "DAMAGER" | false +---@return "TANK" | "HEALER" | "DAMAGER" | "NONE" | false function Unit:GetSpecializationRole(fallback) local specID = self:GetSpecializationID() - if specID then + if type(specID) == "number" then return GetSpecializationRoleByID(specID) end return fallback and self:GetRole() or false @@ -398,6 +415,15 @@ function Unit:CanSee(unit) -- return false -- end -- end + local ignoreLoS = { + [98696] = true -- Illysanna Ravencrest (BRH) + } + if not unit:IsPlayer() then + local id = unit:GetID() + if id and ignoreLoS[id] then + return true + end + end local ax, ay, az = ObjectPosition(self:GetOMToken()) local ah = ObjectHeight(self:GetOMToken()) local attx, atty, attz = GetUnitAttachmentPosition(unit:GetOMToken(), 34) @@ -432,6 +458,8 @@ function Unit:IsCasting() return UnitCastingInfo(self:GetOMToken()) ~= nil end +---@param percent number +---@return number function Unit:GetTimeCastIsAt(percent) local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( self:GetOMToken()) @@ -453,7 +481,7 @@ function Unit:GetTimeCastIsAt(percent) end -- Get Casting or channeling spell ----@return Bastion.Spell | nil +---@return Bastion.Spell | boolean function Unit:GetCastingOrChannelingSpell() local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( self:GetOMToken()) @@ -466,8 +494,7 @@ function Unit:GetCastingOrChannelingSpell() if name then return Bastion.Globals.SpellBook:GetSpell(spellId) end - - return nil + return false end -- Get the end time of the cast or channel @@ -500,6 +527,7 @@ function Unit:IsCastingOrChanneling() return self:IsCasting() or self:IsChanneling() end +---@return Bastion.Unit function Unit:CastTarget() ---@diagnostic disable-next-line: param-type-mismatch return self:IsCastingOrChanneling() and Bastion.UnitManager:Get(ObjectCastingTarget(self:GetOMToken())) or @@ -507,10 +535,12 @@ function Unit:CastTarget() end ---@param unit Bastion.Unit +---@return boolean function Unit:CastTargetIsUnit(unit) return self:IsCastingOrChanneling() and self:CastTarget():IsUnit(unit) end +---@return boolean function Unit:IsImmobilized() return bit.band(self:GetMovementFlag(), 0x400) > 0 end @@ -569,7 +599,7 @@ function Unit:IsInterruptibleAt(percent, ignoreInterruptible) return false end - local percent = percent or math.random(2, 5) + local percent = percent or math.random(2, 20) local castPercent = self:GetChannelOrCastPercentComplete() if castPercent >= percent then @@ -645,6 +675,7 @@ function Unit:IsMoving() return GetUnitSpeed(self:GetOMToken()) > 0 end +---@return TinkrMovementFlags function Unit:GetMovementFlag() return ObjectMovementFlag(self:GetOMToken()) end @@ -786,15 +817,15 @@ end ---@param unit Bastion.Unit ---@return boolean function Unit:InMelee(unit) - local x, y, z = ObjectPosition(self.unit) - local x2, y2, z2 = ObjectPosition(unit.unit) + local x, y, z = ObjectPosition(self:GetOMToken()) + local x2, y2, z2 = ObjectPosition(unit:GetOMToken()) if not x or not x2 then return false end - local scr = ObjectCombatReach(self.unit) - local ucr = ObjectCombatReach(unit.unit) + local scr = ObjectCombatReach(self:GetOMToken()) + local ucr = ObjectCombatReach(unit:GetOMToken()) if not scr or not ucr then return false @@ -808,6 +839,7 @@ function Unit:InMelee(unit) end -- Get object id +---@return number function Unit:GetID() if self.id ~= false then ---@type number @@ -830,6 +862,7 @@ function Unit:IsInRaid() return UnitInRaid(self:GetOMToken()) ~= nil end +---@return boolean function Unit:IsInPartyOrRaid() return self:IsInParty() or self:IsInRaid() end @@ -874,7 +907,7 @@ function Unit:PredictHealth(time) table.remove(self.regression_history, 1) end - table.insert(self.regression_history, { time = GetTime(), percent = self:GetHP() }) + table.insert(self.regression_history, { time = Bastion.Now, percent = self:GetHP() }) for i = 1, #self.regression_history do local entry = self.regression_history[i] @@ -883,7 +916,7 @@ function Unit:PredictHealth(time) end local slope, intercept = self:LinearRegression(x, y) - return slope * time + intercept + return slope * (Bastion.Now + time) + intercept end -- Use linear regression to guess the time until a given health percent @@ -897,7 +930,7 @@ function Unit:PredictTime(percent) table.remove(self.regression_history, 1) end - table.insert(self.regression_history, { time = GetTime(), percent = self:GetHP() }) + table.insert(self.regression_history, { time = Bastion.Now, percent = self:GetHP() }) for i = 1, #self.regression_history do local entry = self.regression_history[i] @@ -926,7 +959,7 @@ end function Unit:TimeToDie() if self:IsDead() then self.regression_history = {} - if type(self.ttd_ticker) == "table" then + if self.ttd_ticker then self.ttd_ticker:Cancel() self.ttd_ticker = false end @@ -955,6 +988,15 @@ function Unit:TimeToDie() return self.ttd end +---@param lower number +---@param upper? number +---@return boolean +function Unit:TTD(lower, upper) + upper = type(upper) == "nil" and lower or upper + local ttd = self:TimeToDie() + return ttd >= lower and ttd <= upper +end + -- Set combat time if affecting combat and return the difference between now and the last time ---@return number function Unit:GetCombatTime() @@ -1053,37 +1095,39 @@ function Unit:GetSwingTimers() return main_speed_remains, off_speed_remains end ----@return nil function Unit:WatchForSwings() - Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() - local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = - CombatLogGetCurrentEventInfo() - - 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 - end - - if subtype:sub(1, 5) == "SWING" and not multistrike then - if subtype == "SWING_MISSED" then - offhand = spellName + if not self.watching_for_swings then + Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() + local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = + CombatLogGetCurrentEventInfo() + + 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 end - local now = GetTime() + if subtype:sub(1, 5) == "SWING" and not multistrike then + if subtype == "SWING_MISSED" then + offhand = spellName + end - if now > self.last_shadow_techniques + 3 then - self.swings_since_sht = self.swings_since_sht + 1 - end + local now = GetTime() - if offhand then - self.last_off_attack = GetTime() - else - self.last_main_attack = GetTime() + if now > self.last_shadow_techniques + 3 then + self.swings_since_sht = self.swings_since_sht + 1 + end + + if offhand then + self.last_off_attack = GetTime() + else + self.last_main_attack = GetTime() + end end end - end - end) + end) + self.watching_for_swings = true + end end -- ismounted @@ -1137,6 +1181,7 @@ function Unit:GetStaggerPercent() end -- Get the units power regen rate +---@return number, number function Unit:GetPowerRegen() ---@diagnostic disable-next-line: redundant-parameter return GetPowerRegen(self:GetOMToken()) @@ -1226,6 +1271,7 @@ function Unit:IsWithinCone(Target, Angle, Distance, rotation) return diff <= Angle and self:GetDistance(Target) <= Distance end +---@return number function Unit:GetEmpoweredStage() local stage = 0 local _, _, _, startTime, _, _, _, spellID, _, numStages = UnitChannelInfo(self:GetOMToken()) @@ -1245,26 +1291,33 @@ function Unit:GetEmpoweredStage() return stage end +---@return boolean function Unit:IsConnected() return UnitIsConnected(self:GetOMToken()) end +---@return boolean function Unit:HasIncomingRessurection() + ---@diagnostic disable-next-line: return-type-mismatch return self:IsDead() and UnitHasIncomingResurrection(self:GetOMToken()) end +---@return WowGameObject | false function Unit:LootTarget() return ObjectLootTarget(self:GetOMToken()) end +---@return boolean function Unit:CanLoot() return ObjectLootable(self:GetOMToken()) end +---@return boolean function Unit:HasTarget() return ObjectTarget(self:GetOMToken()) ~= false end +---@return Bastion.Unit function Unit:Target() return self:HasTarget() and Bastion.UnitManager:Get(ObjectTarget(self:GetOMToken()):unit()) or Bastion.UnitManager:Get("none") @@ -1272,112 +1325,114 @@ end local dummyUnits = { -- City (SW, Orgri, ...) - [31146] = true, -- Raider's Training Dummy - [31144] = true, -- Training Dummy - [32666] = true, -- Training Dummy - [32667] = true, -- Training Dummy - [46647] = true, -- Training Dummy - [114832] = true, -- PvP Training Dummy - [153285] = true, -- Training Dummy - [153292] = true, -- Training Dummy + [31146] = true, -- Raider's Training Dummy + [31144] = true, -- Training Dummy + [32666] = true, -- Training Dummy + [32667] = true, -- Training Dummy + [46647] = true, -- Training Dummy + [114832] = true, -- PvP Training Dummy + [153285] = true, -- Training Dummy + [153292] = true, -- Training Dummy -- MoP Shrine of Two Moons - [67127] = true, -- Training Dummy + [67127] = true, -- Training Dummy -- WoD Alliance Garrison - [87317] = true, -- Mage Tower Damage Training Dummy - [87318] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) - [87320] = true, -- Mage Tower Damage Raider's Training Dummy - [88314] = true, -- Tanking Dungeoneer's Training Dummy - [88316] = true, -- Healing Training Dummy ----> FRIENDLY + [87317] = true, -- Mage Tower Damage Training Dummy + [87318] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) + [87320] = true, -- Mage Tower Damage Raider's Training Dummy + [88314] = true, -- Tanking Dungeoneer's Training Dummy + [88316] = true, -- Healing Training Dummy ----> FRIENDLY -- WoD Horde Garrison - [87760] = true, -- Mage Tower Damage Training Dummy - [87761] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) - [87762] = true, -- Mage Tower Damage Raider's Training Dummy - [88288] = true, -- Tanking Dungeoneer's Training Dummy - [88289] = true, -- Healing Training Dummy ----> FRIENDLY + [87760] = true, -- Mage Tower Damage Training Dummy + [87761] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) + [87762] = true, -- Mage Tower Damage Raider's Training Dummy + [88288] = true, -- Tanking Dungeoneer's Training Dummy + [88289] = true, -- Healing Training Dummy ----> FRIENDLY -- Legion Druid Class Order Hall - [113964] = true, -- Raider's Training Dummy - [113966] = true, -- Dungeoneer's Training Dummy + [113964] = true, -- Raider's Training Dummy + [113966] = true, -- Dungeoneer's Training Dummy -- Legion Mage Class Order Hall - [103397] = true, -- Greater Bullwark Construct - [103404] = true, -- Bullwark Construct - [103402] = true, -- Lesser Bullwark Construct + [103397] = true, -- Greater Bullwark Construct + [103404] = true, -- Bullwark Construct + [103402] = true, -- Lesser Bullwark Construct -- Legion Priest Class Order Hall - [107555] = true, -- Bound void Wraith - [107556] = true, -- Bound void Walker + [107555] = true, -- Bound void Wraith + [107556] = true, -- Bound void Walker -- Legion Rogue Class Order Hall - [92164] = true, -- Training Dummy - [92165] = true, -- Dungeoneer's Training Dummy - [92166] = true, -- Raider's Training Dummy + [92164] = true, -- Training Dummy + [92165] = true, -- Dungeoneer's Training Dummy + [92166] = true, -- Raider's Training Dummy -- Legion Warlock Class Order Hall - [101956] = true, -- Rebellious Fel Lord - [102045] = true, -- Rebellious WrathGuard - [102048] = true, -- Rebellious Felguard - [102052] = true, -- Rebellious imp + [101956] = true, -- Rebellious Fel Lord + [102045] = true, -- Rebellious WrathGuard + [102048] = true, -- Rebellious Felguard + [102052] = true, -- Rebellious imp -- BfA Dazar'Alor - [144081] = true, -- Training Dummy - [144082] = true, -- Training Dummy - [144085] = true, -- Training Dummy - [144086] = true, -- Raider's Training Dummy + [144081] = true, -- Training Dummy + [144082] = true, -- Training Dummy + [144085] = true, -- Training Dummy + [144086] = true, -- Raider's Training Dummy -- BfA Boralus - [126781] = true, -- Training Dummy - [131983] = true, -- Raider's Training Dummy - [131989] = true, -- Training Dummy - [131992] = true, -- Dungeoneer's Training Dummy + [126781] = true, -- Training Dummy + [131983] = true, -- Raider's Training Dummy + [131989] = true, -- Training Dummy + [131992] = true, -- Dungeoneer's Training Dummy -- Shadowlands Kyrian - [154564] = true, -- Valiant's Humility - [154567] = true, -- Purity's Cleaning - [154580] = true, -- Reinforced Guardian - [154583] = true, -- Starlwart Guardian - [154585] = true, -- Valiant's Resolve - [154586] = true, -- Stalwart Phalanx - [160325] = true, -- Humility's Obedience + [154564] = true, -- Valiant's Humility + [154567] = true, -- Purity's Cleaning + [154580] = true, -- Reinforced Guardian + [154583] = true, -- Starlwart Guardian + [154585] = true, -- Valiant's Resolve + [154586] = true, -- Stalwart Phalanx + [160325] = true, -- Humility's Obedience -- Shadowlands Venthyr - [173942] = true, -- Training Dummy - [175449] = true, -- Raider's Training Dummy - [175450] = true, -- Dungeoneer's Training Dummy - [175451] = true, -- Dungeoneer's Tanking Dummy - [175452] = true, -- Raider's Tanking Dummy - [175455] = true, -- Cleave Training Dummy - [175456] = true, -- Swarm Training Dummy - [175462] = true, -- Sinfall Fiend + [173942] = true, -- Training Dummy + [175449] = true, -- Raider's Training Dummy + [175450] = true, -- Dungeoneer's Training Dummy + [175451] = true, -- Dungeoneer's Tanking Dummy + [175452] = true, -- Raider's Tanking Dummy + [175455] = true, -- Cleave Training Dummy + [175456] = true, -- Swarm Training Dummy + [175462] = true, -- Sinfall Fiend -- Shadowlands Night Fae - [174565] = true, -- Dungeoneer's Tanking Dummy - [174566] = true, -- Raider's Tanking Dummy - [174567] = true, -- Raider's Training Dummy - [174568] = true, -- Dungeoneer's Training Dummy - [174569] = true, -- Training Dummy - [174570] = true, -- Swarm Training Dummy - [174571] = true, -- Cleave Training Dummy + [174565] = true, -- Dungeoneer's Tanking Dummy + [174566] = true, -- Raider's Tanking Dummy + [174567] = true, -- Raider's Training Dummy + [174568] = true, -- Dungeoneer's Training Dummy + [174569] = true, -- Training Dummy + [174570] = true, -- Swarm Training Dummy + [174571] = true, -- Cleave Training Dummy -- Shadowlands Necrolord - [174484] = true, -- Dungeoneer's Training Dummy - [174487] = true, -- Training Dummy - [174488] = true, -- Raider's Training Dummy - [174491] = true, -- Tanking Dummy + [174484] = true, -- Dungeoneer's Training Dummy + [174487] = true, -- Training Dummy + [174488] = true, -- Raider's Training Dummy + [174491] = true, -- Tanking Dummy -- DargonFlight Valdrakken - [198594] = true, -- Cleave Training Dummy - [194648] = true, -- Training Dummy - [189632] = true, -- Animated Duelist - [194643] = true, -- Dungeoneer's Training Dummy - [194644] = true, -- Dungeoneer's Training Dummy - [197833] = true, -- PvP Training Dummy - [189617] = true, -- Boulderfist - [194649] = true, -- Normal Tank Dummy + [198594] = true, -- Cleave Training Dummy + [194648] = false, -- Training Dummy + [189632] = true, -- Animated Duelist + [194643] = true, -- Dungeoneer's Training Dummy + [194644] = true, -- Dungeoneer's Training Dummy + [197833] = true, -- PvP Training Dummy + [189617] = true, -- Boulderfist + [194649] = true, -- Normal Tank Dummy -- DargonFlight Iskaara - [193563] = true, -- Training Dummy + [193563] = true, -- Training Dummy -- Other - [65310] = true, -- Turnip Punching Bag (toy) - [66374] = true, -- Anatomical Dummy (toy) - [196394] = true, -- Tuskarr Training Dummy (toy) - [196406] = true, -- Rubbery Fish Head (toy) - [199057] = true, -- Black Dragon's Challenge Dummy (toy) + [65310] = true, -- Turnip Punching Bag (toy) + [66374] = true, -- Anatomical Dummy (toy) + [196394] = true, -- Tuskarr Training Dummy (toy) + [196406] = true, -- Rubbery Fish Head (toy) + [199057] = true, -- Black Dragon's Challenge Dummy (toy) } +---@return boolean function Unit:IsDummy() local npcId = self:GetID() return npcId and npcId >= 0 and dummyUnits[npcId] == true or false end ---@param npcId? number +---@return boolean function Unit:IsInBossList(npcId) npcId = npcId or self:GetID() for i = 1, 4 do @@ -1386,9 +1441,11 @@ function Unit:IsInBossList(npcId) return true end end + return false end ---@param npcId number +---@return number function Unit:SpecialTTDPercentage(npcId) local specialTTDPercentage = Bastion.TimeToDie.specialTTDPercentageData[npcId] if not specialTTDPercentage then return 0 end @@ -1402,6 +1459,7 @@ end ---@param npcId? number ---@param hp? number +---@return boolean function Unit:CheckHPFromBossList(npcId, hp) npcId = npcId or self:GetID() local thisHP = hp or 100 @@ -1415,13 +1473,16 @@ function Unit:CheckHPFromBossList(npcId, hp) return false end +---@param percentage number +---@param minSamples? number +---@return Bastion.TimeToDie.Enums | integer function Unit:TimeToX(percentage, minSamples) - if self:IsDummy() then return 6666 end - if self:IsPlayer() and Bastion.UnitManager:Get("player"):CanAttack(self) then return 25 end - local seconds = 8888 + --if self:IsDummy() then return 6666 end + if self:IsPlayer() and Bastion.UnitManager:Get("player"):CanAttack(self) then return Bastion.TimeToDie.Enums.PLAYER end + local seconds = 0 local unitGuid = self:GetGUID() if not unitGuid then - return seconds + return Bastion.TimeToDie.Enums.NO_GUID end local unitTable = Bastion.TimeToDie.Units[unitGuid] -- Simple linear regression @@ -1456,8 +1517,8 @@ function Unit:TimeToX(percentage, minSamples) -- Use best fit line to calculate estimated time to reach target health seconds = (percentage - a) / b -- Subtract current time to obtain "time remaining" - seconds = math.min(7777, seconds - (GetTime() - unitTable[2])) - if seconds < 0 then seconds = 9999 end + seconds = seconds - (GetTime() - unitTable[2]) + if seconds < 0 then seconds = Bastion.TimeToDie.Enums.NEGATIVE_TTD end end end end @@ -1465,12 +1526,15 @@ function Unit:TimeToX(percentage, minSamples) end ---@param minSamples? number +---@return Bastion.TimeToDie.Enums | integer function Unit:TimeToDie2(minSamples) if not self:Exists() then - return 11111 + return Bastion.TimeToDie.Enums.DOES_NOT_EXIST end local unitGuid = self:GetGUID() - if not unitGuid then return 11111 end + if not unitGuid then + return Bastion.TimeToDie.Enums.NO_GUID + end minSamples = minSamples or 3 ---@type {TTD: {[number]: number}} @@ -1482,23 +1546,28 @@ function Unit:TimeToDie2(minSamples) ttd = {} unitInfo.TTD = ttd end + local v if not ttd[minSamples] then - ttd[minSamples] = self:TimeToX(self:SpecialTTDPercentage(self:GetID()), minSamples) + v = self:TimeToX(self:SpecialTTDPercentage(self:GetID()), minSamples) + if v >= 0 then + ttd[minSamples] = v + Bastion.Globals.UnitInfo:Set(unitGuid, unitInfo, .5) + end end - Bastion.Globals.UnitInfo:Set(unitGuid, unitInfo, .5) - return ttd[minSamples] + return ttd[minSamples] or v end -- Get the boss unit TimeToDie ---@param minSamples? number +---@return Bastion.TimeToDie.Enums | integer function Unit:BossTimeToDie(minSamples) if self:IsInBossList() or self:IsDummy() then return self:TimeToDie2(minSamples) end - return 11111 + return Bastion.TimeToDie.Enums.DOES_NOT_EXIST end -- Get if the unit meets the TimeToDie requirements. @@ -1507,10 +1576,11 @@ end ---@param offset number ---@param valueThreshold number ---@param minSamples? number +---@return boolean function Unit:FilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) local TTD = self:TimeToDie2(minSamples) - return TTD < (valueThreshold or 7777) and Bastion.Utils.CompareThis(operator, TTD + (offset or 0), value) or false + return TTD > -1 and TTD < valueThreshold and Bastion.Utils.CompareThis(operator, TTD + (offset or 0), value) or false end -- Get if the boss unit meets the TimeToDie requirements. @@ -1519,6 +1589,7 @@ end ---@param offset number ---@param valueThreshold number ---@param minSamples? number +---@return boolean function Unit:BossFilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) if self:IsInBossList() or self:IsDummy() then return self:FilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) @@ -1529,12 +1600,14 @@ end -- Get if the Time To Die is Valid for an Unit (i.e. not returning a warning code). ---@param minSamples? number +---@return boolean function Unit:TimeToDieIsNotValid(minSamples) - return self:TimeToDie2(minSamples) >= 7777 + return self:TimeToDie2(minSamples) > -1 end -- Get if the Time To Die is Valid for a boss Unit (i.e. not returning a warning code or not being a boss). ---@param minSamples? number +---@return boolean function Unit:BossTimeToDieIsNotValid(minSamples) if self:IsInBossList() then return self:TimeToDieIsNotValid(minSamples) @@ -1543,62 +1616,4 @@ function Unit:BossTimeToDieIsNotValid(minSamples) return true end --- local empowering = {} - --- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_START", function(...) --- local unit, unk, id = ... --- if not unit then return end --- local guid = ObjectGUID(unit) --- if not guid then return end --- empowering[guid] = -1 --- end) - --- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_STOP", function(...) --- local unit, unk, id = ... --- if not unit then return end --- local guid = ObjectGUID(unit) --- if not guid then return end --- empowering[guid] = -1 --- end) - --- function Unit:GetUnitEmpowerStage() --- local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages = --- UnitChannelInfo(self:GetOMToken()); - --- if name and empowering[self:GetGUID()] == -1 then --- empowering[self:GetGUID()] = numStages --- end - --- if not name and empowering[self:GetGUID()] then --- return empowering[self:GetGUID()] --- end - --- if not name then --- return empowering[self:GetGUID()] --- end - --- local getStageDuration = function(stage) --- if stage == numStages then --- return GetUnitEmpowerHoldAtMaxTime(self:GetOMToken()); --- else --- return GetUnitEmpowerStageDuration(self:GetOMToken(), stage - 1); --- end --- end - --- local time = GetTime() - (startTime / 1000); - --- local higheststage = 0 --- local sumdur = 0 --- for i = 1, numStages - 1, 1 do --- local duration = getStageDuration(i) / 1000; --- sumdur = sumdur + duration - --- if time > sumdur then --- higheststage = i --- end --- end - --- return higheststage --- end - return Unit diff --git a/src/UnitManager/UnitManager.lua b/src/UnitManager/UnitManager.lua index f928630..9462923 100644 --- a/src/UnitManager/UnitManager.lua +++ b/src/UnitManager/UnitManager.lua @@ -11,7 +11,7 @@ local Unit = Bastion.Unit ---@class Bastion.UnitManager ---@field units table ---@field customUnits table ----@field objects table +---@field objects table ---@field cache Bastion.Cache local UnitManager = { units = {}, @@ -29,7 +29,7 @@ function UnitManager:__index(k) return UnitManager[k] end - local k = k or "none" + k = k or "none" -- if custom unit exists, return it it's cache expired return a new one if self.customUnits[k] then @@ -109,7 +109,7 @@ end -- Get a unit by guid ---@param guid string | WowGameObject ----@return Bastion.Unit +---@return Bastion.Unit? function UnitManager:GetObject(guid) return self.objects[guid] end diff --git a/src/Vector3/Vector3.lua b/src/Vector3/Vector3.lua index ca9663e..8c12145 100644 --- a/src/Vector3/Vector3.lua +++ b/src/Vector3/Vector3.lua @@ -323,7 +323,7 @@ end ---@return Bastion.Vector3 function Vector3:Project(onNormal) local num = onNormal:Dot(onNormal) - if num < 1.401298E-45 then + if num < 1.401298e-45 then return Vector3:New(0, 0, 0) end diff --git a/src/_bastion.lua b/src/_bastion.lua index 6b38661..ef89444 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -111,9 +111,8 @@ local exampleNames = { "ExampleModule.lua", } +---@param dir string local function Load(dir) - local dir = dir - if dir:sub(1, 1) == "@" then dir = dir:sub(2) dir = string.format("%s/%s", BastionScriptsBase, dir) @@ -205,6 +204,11 @@ Bastion.Notifications = Bastion.NotificationList:New() Bastion.Config = Bastion.require("Bastion.Config") Bastion.TimeToDie = Bastion.require("Bastion.TimeToDie") +---@param unitTarget UnitId +Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_HEALTH", function(unitTarget) + --Bastion.UnitManager:Get(unitTarget):UpdateHealth() +end) + ---@enum (key) CompareThisTable local compareThisTable = { @@ -227,12 +231,16 @@ Bastion.Utils = { end } +---@type table local LIBRARIES = {} + ---@type Bastion.Module[] local MODULES = {} Bastion.Enabled = false +---@param unit UnitToken +---@param auras UnitAuraUpdateInfo Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) ---@type Bastion.Unit | nil local u = Bastion.UnitManager:Get(unit) @@ -243,6 +251,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) end) Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) + ---@type UnitIds, string, spellId local unit, castGUID, spellID = ... local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID) @@ -255,7 +264,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", functi end end) -local pguid = UnitGUID("player") +local playerGuid = UnitGUID("player") local missed = {} ---@class Bastion.Globals.SpellName : { [spellId]: string } @@ -303,7 +312,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", fun if u2 then u2:SetLastCombatTime(t) - if subEvent == "SPELL_MISSED" and sourceGUID == pguid and spellID == 408 then + if subEvent == "SPELL_MISSED" and sourceGUID == playerGuid and spellID == 408 then local missType = args[15] if missType == "IMMUNE" then @@ -318,10 +327,18 @@ Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", fun end end end) + local Timer = { TTD = 0 } + +Bastion.Now = GetTime() +Bastion.Tick = GetGameTick() + Bastion.Ticker = C_Timer.NewTicker(0.1, function() + Bastion.Now = GetTime() + Bastion.Tick = GetGameTick() + if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then Bastion.CombatTimer:Start() elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then @@ -330,8 +347,8 @@ Bastion.Ticker = C_Timer.NewTicker(0.1, function() if Bastion.Enabled then Bastion.ObjectManager:Refresh() - if GetTime() > Timer.TTD then - Timer.TTD = GetTime() + Bastion.TimeToDie.Settings.Refresh + if Bastion.Now > Timer.TTD then + Timer.TTD = Bastion.Now + Bastion.TimeToDie.Settings.Refresh Bastion.TimeToDie:Refresh() end for i = 1, #MODULES do @@ -340,6 +357,7 @@ Bastion.Ticker = C_Timer.NewTicker(0.1, function() end end) +---@param module Bastion.Module function Bastion:Register(module) table.insert(MODULES, module) Bastion:Print("Registered", module) @@ -352,11 +370,10 @@ function Bastion:FindModule(name) return MODULES[i] end end - - return nil end Bastion.PrintEnabled = false + function Bastion:Print(...) if not Bastion.PrintEnabled then return @@ -494,6 +511,7 @@ function Bastion:CheckLibraryDependencies() return true end +---@param library string function Bastion:Import(library) local lib = self:GetLibrary(library) @@ -504,6 +522,7 @@ function Bastion:Import(library) return lib:Resolve() end +---@param name string function Bastion:GetLibrary(name) if not LIBRARIES[name] then error("Library " .. name .. " not found")