Updates to TTD.

main
ck 11 months ago
parent ae0beddcca
commit fb3f8decb6
  1. 5
      src/APL/APL.lua
  2. 13
      src/APLActor/APLActor.lua
  3. 56
      src/EventManager/EventManager.lua
  4. 181
      src/Item/Item.lua
  5. 7
      src/ItemBook/ItemBook.lua
  6. 18
      src/Module/Module.lua
  7. 2
      src/MythicPlusUtils/MythicPlusUtils.lua
  8. 9
      src/ObjectManager/ObjectManager.lua
  9. 80
      src/Spell/Spell.lua
  10. 25
      src/TimeToDie/TimeToDie.lua
  11. 403
      src/Unit/Unit.lua
  12. 6
      src/UnitManager/UnitManager.lua
  13. 2
      src/Vector3/Vector3.lua
  14. 35
      src/_bastion.lua

@ -150,12 +150,9 @@ end
-- Add an APL to the APL (for sub APLs) -- Add an APL to the APL (for sub APLs)
---@param apl Bastion.APL ---@param apl Bastion.APL
---@param condition fun(...):boolean ---@param condition? fun(...):boolean
---@return Bastion.APLActor ---@return Bastion.APLActor
function APL:AddAPL(apl, condition) 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({ local actor = Bastion.APLActor:New({
type = "apl", type = "apl",
apl = apl, apl = apl,

@ -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 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 if actorTable.type == "sequencer" then
---@cast actorTable Bastion.APL.Actor.Sequencer.Table ---@cast actorTable Bastion.APL.Actor.Sequencer.Table
if actorTable.condition and 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
if not actorTable.condition and not actorTable.sequencer:Finished() then
actorTable.sequencer:Execute() actorTable.sequencer:Execute()
return true return true
end end
@ -97,11 +92,9 @@ function APLActor:Execute()
end end
if actorTable.type == "apl" then if actorTable.type == "apl" then
---@cast actorTable Bastion.APL.Actor.APL.Table ---@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) -- print("Bastion: APL:Execute: Executing sub APL " .. actorTable.apl.name)
if actorTable.apl:Execute() then return actorTable.apl:Execute()
return true
end
end end
end end
if actorTable.type == "spell" then if actorTable.type == "spell" then

@ -6,9 +6,10 @@ local Tinkr, Bastion = ...
---@field frame Frame ---@field frame Frame
---@field events table<string, { [number]: fun(...) }> ---@field events table<string, { [number]: fun(...) }>
---@field eventHandlers table<string, { [number]: fun(...) }> ---@field eventHandlers table<string, { [number]: fun(...) }>
---@field wowEventHandlers table<string, { [number]: fun(...) }> ---@field wowEventHandlers table<WowEvent, { [number]: fun(...) }>
---@field selfCombatEventHandlers table<string, { [number]: fun(...) }> ---@field selfCombatEventHandlers table<string, { [number]: fun(...) }>
---@field CombatEventHandlers table<string, { [number]: fun(...) }> ---@field CombatEventHandlers table<string, { [number]: fun(...) }>
---@field playerGUID string|boolean
local EventManager = { local EventManager = {
events = {}, events = {},
eventHandlers = {}, eventHandlers = {},
@ -27,6 +28,7 @@ function EventManager:New()
self.wowEventHandlers = {} self.wowEventHandlers = {}
self.selfCombatEventHandlers = {} self.selfCombatEventHandlers = {}
self.CombatEventHandlers = {} self.CombatEventHandlers = {}
self.playerGUID = UnitGUID("player") or false
-- Frame for wow events -- Frame for wow events
self.frame = CreateFrame("Frame") self.frame = CreateFrame("Frame")
@ -59,20 +61,14 @@ function EventManager:RegisterEvent(event, handler)
end end
-- Register a wow event -- Register a wow event
---@param event string | string[] ---@param events WowEvent | WowEvent[]
---@param handler fun(...) ---@param handler fun(...)
---@return nil function EventManager:RegisterWoWEvent(events, handler)
function EventManager:RegisterWoWEvent(event, handler) if type(events) == "string" then
if type(event) == "table" then events = { events }
for _, e in ipairs(event) do end
if not self.wowEventHandlers[e] then
self.wowEventHandlers[e] = {}
self.frame:RegisterEvent(e)
end
table.insert(self.wowEventHandlers[e], handler) for _, event in ipairs(events) do
end
else
if not self.wowEventHandlers[event] then if not self.wowEventHandlers[event] then
self.wowEventHandlers[event] = {} self.wowEventHandlers[event] = {}
self.frame:RegisterEvent(event) self.frame:RegisterEvent(event)
@ -94,33 +90,33 @@ function EventManager:TriggerEvent(event, ...)
end end
end end
---@param subevent string | string[] ---@param subevents string | string[]
---@param handler fun(...) ---@param handler fun(...)
function EventManager:RegisterSelfCombatEvent(subevent, handler) function EventManager:RegisterSelfCombatEvent(subevents, handler)
if type(subevent) == "string" then if type(subevents) == "string" then
subevent = { subevent } subevents = { subevents }
end end
for _, e in ipairs(subevent) do for _, subevent in ipairs(subevents) do
if not self.selfCombatEventHandlers[e] then if not self.selfCombatEventHandlers[subevent] then
self.selfCombatEventHandlers[e] = {} self.selfCombatEventHandlers[subevent] = {}
end end
table.insert(self.selfCombatEventHandlers[e], handler) table.insert(self.selfCombatEventHandlers[subevent], handler)
end end
end end
---@param subevent string | string[] ---@param subevents string | string[]
---@param handler fun(...) ---@param handler fun(...)
function EventManager:RegisterCombatEvent(subevent, handler) function EventManager:RegisterCombatEvent(subevents, handler)
if type(subevent) == "string" then if type(subevents) == "string" then
subevent = { subevent } subevents = { subevents }
end end
for _, e in ipairs(subevent) do for _, subevent in ipairs(subevents) do
if not self.CombatEventHandlers[e] then if not self.CombatEventHandlers[subevent] then
self.CombatEventHandlers[e] = {} self.CombatEventHandlers[subevent] = {}
end end
table.insert(self.CombatEventHandlers[e], handler) table.insert(self.CombatEventHandlers[subevent], handler)
end end
end end
@ -128,7 +124,7 @@ end
---@param subevent string ---@param subevent string
---@param ... any ---@param ... any
function EventManager:CLEUHandler(timestamp, subevent, ...) 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 for _, callback in ipairs(self.selfCombatEventHandlers[subevent]) do
callback(timestamp, subevent, ...) callback(timestamp, subevent, ...)
end end

@ -1,23 +1,37 @@
---@type Tinkr, Bastion ---@type Tinkr, Bastion
local 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 -- Create a new Item class
---@class Bastion.Item ---@class Bastion.Item
---@field ItemID number ---@field itemID number
---@field UsableIfFunc boolean | fun(self:Bastion.Item):boolean ---@field UsableIfFunc boolean | fun(self:Bastion.Item):boolean
---@field PreUseFunc boolean | fun(self:Bastion.Item) ---@field PreUseFunc boolean | fun(self:Bastion.Item)
---@field target boolean | Bastion.Unit ---@field target Bastion.Unit | false
---@field conditions table<string, { func: fun(self:Bastion.Item):boolean }> ---@field conditions table<string, { func: fun(self:Bastion.Item):boolean }>
---@field OnUseFunc boolean | fun(self:Bastion.Item) ---@field OnUseFunc boolean | fun(self:Bastion.Item)
---@field spellID number | nil ---@field spellID number | nil
---@field lastUpdated boolean|number
---@field playerUsable boolean
---@field shouldUpdate boolean
local Item = { local Item = {
UsableIfFunc = false, UsableIfFunc = false,
PreUseFunc = false, PreUseFunc = false,
OnUseFunc = false, OnUseFunc = false,
wasLooking = false, wasLooking = false,
lastUpdated = false,
lastUseAttempt = 0, lastUseAttempt = 0,
conditions = {}, conditions = {},
target = false,
} }
local usableExcludes = { local usableExcludes = {
@ -27,14 +41,13 @@ local usableExcludes = {
---@param itemId number | string ---@param itemId number | string
---@return number charges, number maxCharges, number start, number duration ---@return number charges, number maxCharges, number start, number duration
local GetItemCharges = function(itemId) local GetItemCharges = function(itemId)
local spellId = select(2, GetItemSpell(itemId)) local _, spellId = GetItemSpell(itemId)
local charges, maxCharges, start, duration, chargeModRate = GetSpellCharges(spellId) local charges, maxCharges, start, duration, chargeModRate = GetSpellCharges(spellId)
return charges, maxCharges, start, duration return charges, maxCharges, start, duration
end end
function Item:__index(k) function Item:__index(k)
local response = Bastion.ClassMagic:Resolve(Item, k) local response = Bastion.ClassMagic:Resolve(Item, k)
if response == nil then if response == nil then
response = rawget(self, k) response = rawget(self, k)
end end
@ -62,24 +75,113 @@ end
-- Constructor -- Constructor
---@param id number ---@param id number
function Item:New(id) function Item:New(id)
---@class Bastion.Item
local self = setmetatable({}, 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 function Item:ShouldUpdate()
local name, spellID = GetItemSpell(self:GetID()) if self.shouldUpdate and not self.lastUpdated or GetTime() - self.lastUpdated > 30 then
if spellID then return true
self.spellID = spellID
Bastion.Globals.SpellBook:GetSpell(spellID)
end 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 return self
end 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 -- Get the Items id
---@return number ---@return number
function Item:GetID() function Item:GetID()
return self.ItemID return self.itemID
end end
-- Get the Items name -- Get the Items name
@ -126,7 +228,17 @@ function Item:GetCooldownRemaining()
end end
-- Use the Item -- 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 ---@param condition? string | fun(self:Bastion.Item):boolean
---@return boolean ---@return boolean
function Item:Use(unit, condition) function Item:Use(unit, condition)
@ -147,12 +259,14 @@ function Item:Use(unit, condition)
self:GetPreUseFunction()(self) self:GetPreUseFunction()(self)
end 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 -- Check if the mouse was looking
self.wasLooking = IsMouselooking() self.wasLooking = IsMouselooking()
UseItemByName(self.traits.use.byId and self:GetID() or self:GetName(), target:GetOMToken())
-- Use the Item
UseItemByName(self:GetName(), unit:GetOMToken())
Bastion:Debug("Using", self) Bastion:Debug("Using", self)
-- Set the last Use time -- Set the last Use time
@ -186,7 +300,8 @@ end
-- Check if the Item is on cooldown -- Check if the Item is on cooldown
---@return boolean ---@return boolean
function Item:IsOnCooldown() function Item:IsOnCooldown()
return select(2, C_Container.GetItemCooldown(self:GetID())) > 0 local _, duration = C_Container.GetItemCooldown(self:GetID())
return duration > 0
end end
-- Check if the Item is usable -- Check if the Item is usable
@ -211,6 +326,9 @@ end
-- Check if the Item is Usable -- Check if the Item is Usable
---@return boolean ---@return boolean
function Item:Usable() function Item:Usable()
if not self:EvaluateTraits() then
return false
end
if self:GetUsableFunction() then if self:GetUsableFunction() then
return self:GetUsableFunction()(self) return self:GetUsableFunction()(self)
end end
@ -283,7 +401,18 @@ end
---@param unit Bastion.Unit ---@param unit Bastion.Unit
---@return boolean ---@return boolean
function Item:IsInRange(unit) 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()) local them = Object(unit:GetOMToken())
@ -311,7 +440,7 @@ function Item:IsInRange(unit)
return true return true
end end
return false return false ]]
end end
-- Get the last use time -- Get the last use time
@ -334,6 +463,10 @@ function Item:GetCharges()
return select(1, GetItemCharges(self:GetID())) return select(1, GetItemCharges(self:GetID()))
end end
function Item:GetCount()
return GetItemCount(self:GetID(), false, false, false)
end
-- Get the Items charges remaining -- Get the Items charges remaining
---@return number ---@return number
function Item:GetChargesRemaining() function Item:GetChargesRemaining()
@ -389,16 +522,18 @@ end
-- Set the Items target -- Set the Items target
---@param unit Bastion.Unit ---@param unit Bastion.Unit
---@return Bastion.Item
function Item:SetTarget(unit) function Item:SetTarget(unit)
self.target = unit self.target = unit
return self return self
end end
-- Get the Items target -- Get the Items target
---@return Bastion.Unit | boolean
function Item:GetTarget() 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 end
-- IsMagicDispel -- IsMagicDispel

@ -14,6 +14,13 @@ ItemBook.__index = ItemBook
function ItemBook:New() function ItemBook:New()
local self = setmetatable({}, ItemBook) local self = setmetatable({}, ItemBook)
self.items = {} 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 return self
end end

@ -1,9 +1,14 @@
---@type Tinkr, Bastion
local Tinkr, Bastion = ...
-- Create a module class for a bastion module -- Create a module class for a bastion module
---@class Bastion.Module ---@class Bastion.Module
---@field name string ---@field name string
---@field enabled boolean ---@field enabled boolean
---@field synced function[] ---@field synced function[]
---@field interval? number
---@field nextTick number
local Module = {} local Module = {}
Module.__index = Module Module.__index = Module
@ -15,15 +20,17 @@ end
-- Constructor -- Constructor
---@param name string ---@param name string
---@param interval? number
---@return Bastion.Module ---@return Bastion.Module
function Module:New(name) function Module:New(name, interval)
local module = {} local module = {}
setmetatable(module, Module) setmetatable(module, Module)
module.name = name module.name = name
module.enabled = false module.enabled = false
module.synced = {} module.synced = {}
module.interval = interval
module.nextTick = 0
return module return module
end end
@ -67,8 +74,11 @@ end
-- Sync -- Sync
function Module:Tick() function Module:Tick()
if self.enabled then if self.enabled then
for i = 1, #self.synced do if Bastion.Tick >= self.nextTick then
self.synced[i]() self.nextTick = Bastion.Tick + (self.interval or 1)
for i = 1, #self.synced do
self.synced[i]()
end
end end
end end
end end

@ -1300,7 +1300,7 @@ function MythicPlusUtils:CastingCriticalKick(unit, percent)
end end
---@param unit Bastion.Unit ---@param unit Bastion.Unit
---@param percent number ---@param percent? number
---@return boolean ---@return boolean
function MythicPlusUtils:CastingCriticalStun(unit, percent) function MythicPlusUtils:CastingCriticalStun(unit, percent)
local castingSpell = unit:GetCastingOrChannelingSpell() local castingSpell = unit:GetCastingOrChannelingSpell()

@ -92,8 +92,8 @@ function ObjectManager:Refresh()
for _, object in pairs(objects) do for _, object in pairs(objects) do
self:EnumLists(object) self:EnumLists(object)
local objectType = ObjectType(object)
if ({ [5] = true, [6] = true, [7] = true })[ObjectType(object)] then if ({ [5] = true, [6] = true, [7] = true })[objectType] then
local objectGUID = ObjectGUID(object) local objectGUID = ObjectGUID(object)
if objectGUID then if objectGUID then
local unit = Bastion.UnitManager:GetObject(objectGUID) local unit = Bastion.UnitManager:GetObject(objectGUID)
@ -102,7 +102,8 @@ function ObjectManager:Refresh()
Bastion.UnitManager:SetObject(unit) Bastion.UnitManager:SetObject(unit)
end 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) self.critters:push(unit)
elseif unit:GetID() == 204560 then elseif unit:GetID() == 204560 then
self.incorporeal:push(unit) self.incorporeal:push(unit)
@ -110,7 +111,7 @@ function ObjectManager:Refresh()
self.afflicted:push(unit) self.afflicted:push(unit)
elseif unit:GetID() == 120651 then elseif unit:GetID() == 120651 then
self.explosives:push(unit) 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) self.friends:push(unit)
elseif unit:IsEnemy() then elseif unit:IsEnemy() then
self.enemies:push(unit) self.enemies:push(unit)

@ -9,6 +9,15 @@ local Tinkr, Bastion = ...
---@field channeling boolean ---@field channeling boolean
---@field override boolean ---@field override boolean
---@field talent boolean | spellId ---@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 ---@class Bastion.Spell.Traits.Target
---@field exists boolean ---@field exists boolean
@ -17,13 +26,16 @@ local Tinkr, Bastion = ...
---@class Bastion.Spell.Traits ---@class Bastion.Spell.Traits
---@field cast Bastion.Spell.Traits.Cast ---@field cast Bastion.Spell.Traits.Cast
---@field target Bastion.Spell.Traits.Target ---@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 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.Target.Params : Bastion.Spell.Traits.Target, { [string]?: boolean }
---@class Spell.Traits.Cost.Params : Bastion.Spell.Traits.Cost[]
---@class Bastion.Spell.Traits.Params ---@class Bastion.Spell.Traits.Params
---@field cast? Bastion.Spell.Traits.Cast.Params ---@field cast? Bastion.Spell.Traits.Cast.Params
---@field target? Spell.Traits.Target.Params ---@field target? Spell.Traits.Target.Params
---@field cost? Spell.Traits.Cost.Params
---@class Bastion.Spell.Aura ---@class Bastion.Spell.Aura
---@field spell Bastion.Spell ---@field spell Bastion.Spell
@ -49,7 +61,17 @@ local Tinkr, Bastion = ...
---@field target Bastion.Unit | false ---@field target Bastion.Unit | false
---@field traits Bastion.Spell.Traits ---@field traits Bastion.Spell.Traits
---@field wasLooking boolean ---@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 = { local usableExcludes = {
[18562] = true, [18562] = true,
@ -86,16 +108,7 @@ function Spell:New(id)
---@class Bastion.Spell ---@class Bastion.Spell
local self = setmetatable({}, Spell) local self = setmetatable({}, Spell)
self.auras = {} self.auras = {}
self.CastableIfFunc = false
self.damage = 0
self.damageFormula = false
self.lastCastAt = false
self.lastCastAttempt = false
self.OnCastFunc = false
self.overrides = {} self.overrides = {}
self.PostCastFunc = false
self.PreCastFunc = false
self.release_at = false
self.conditions = {} self.conditions = {}
self.spellID = id self.spellID = id
self.traits = { self.traits = {
@ -107,11 +120,13 @@ function Spell:New(id)
channeling = false, channeling = false,
override = false, override = false,
talent = false, talent = false,
power = false,
}, },
target = { target = {
exists = true, exists = true,
player = false, player = false,
} },
cost = {},
} }
self.target = false self.target = false
self.wasLooking = false self.wasLooking = false
@ -218,6 +233,10 @@ end
---@param condition? string | fun(self:Bastion.Spell):boolean ---@param condition? string | fun(self:Bastion.Spell):boolean
---@return boolean ---@return boolean
function Spell:Cast(unit, condition) function Spell:Cast(unit, condition)
if not self:Castable() then
return false
end
if condition then 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 return false
@ -226,9 +245,6 @@ function Spell:Cast(unit, condition)
end end
end end
if not self:Castable() then
return false
end
-- Call pre cast function -- Call pre cast function
if self:GetPreCastFunction() then if self:GetPreCastFunction() then
@ -351,12 +367,22 @@ function Spell:SetTraits(traits)
end end
end 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 return self
end end
function Spell:EvaluateTraits() function Spell:EvaluateTraits()
local player = Bastion.UnitManager:Get("player") 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 if not self.traits.cast.global and player:GetGCD() > 0 then
return false return false
end end
@ -678,6 +704,27 @@ function Spell:IsSpell(spell)
return self:GetID() == spell:GetID() return self:GetID() == spell:GetID()
end 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 -- GetCost
---@return number ---@return number
function Spell:GetCost() function Spell:GetCost()
@ -705,9 +752,12 @@ function Spell:Damage()
end end
end end
---@param target Bastion.Unit ---@param target? Bastion.Unit
---@param source? "any" | Bastion.Unit ---@param source? "any" | Bastion.Unit
function Spell:GetAura(target, source) function Spell:GetAura(target, source)
if type(target) == "nil" then
target = Bastion.UnitManager:Get("player")
end
if type(source) == "nil" then if type(source) == "nil" then
source = Bastion.UnitManager:Get("player") source = Bastion.UnitManager:Get("player")
end end

@ -25,7 +25,23 @@ local TimeToDie = {
Units = {}, -- Used to track units, Units = {}, -- Used to track units,
---@type table<string, boolean> ---@type table<string, boolean>
ExistingUnits = {}, -- Used to track GUIDs of currently existing units (to be compared with tracked units) 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() function TimeToDie:IterableUnits()
@ -216,6 +232,7 @@ TimeToDie.specialTTDPercentageData = {
---@param enemies? Bastion.List ---@param enemies? Bastion.List
---@param bossOnly? boolean ---@param bossOnly? boolean
function TimeToDie.FightRemains(enemies, bossOnly) function TimeToDie.FightRemains(enemies, bossOnly)
---@type boolean, number
local bossExists, maxTimeToDie local bossExists, maxTimeToDie
for i = 1, 4 do for i = 1, 4 do
local bossUnit = Bastion.UnitManager:Get(string.format("boss%d", i)) 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 bossExists or bossOnly then
-- If we have a boss list but no valid boss time, return invalid -- 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 end
-- If we specify an AoE range, iterate through all the targets in the specified range -- 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 -- Get if the Time To Die is Valid for a boss fight remains
function TimeToDie.BossFightRemainsIsNotValid() function TimeToDie.BossFightRemainsIsNotValid()
return TimeToDie.BossFightRemains() >= 7777 return TimeToDie.BossFightRemains() < 0
end end
-- Returns if the current fight length meets the requirements. -- Returns if the current fight length meets the requirements.
@ -267,7 +284,7 @@ end
---@param bossOnly boolean ---@param bossOnly boolean
function TimeToDie.FilteredFightRemains(enemies, operator, value, checkIfValid, bossOnly) function TimeToDie.FilteredFightRemains(enemies, operator, value, checkIfValid, bossOnly)
local fightRemains = TimeToDie.FightRemains(enemies, bossOnly) local fightRemains = TimeToDie.FightRemains(enemies, bossOnly)
if checkIfValid and fightRemains >= 7777 then if checkIfValid and fightRemains < 0 then
return false return false
end end

@ -16,8 +16,23 @@ local Unit = {
ttd_ticker = false, ttd_ticker = false,
ttd = 0, ttd = 0,
id = false, --[[ @asnumber ]] 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) function Unit:__index(k)
local response = Bastion.ClassMagic:Resolve(Unit, k) local response = Bastion.ClassMagic:Resolve(Unit, k)
@ -56,6 +71,7 @@ end
-- Constructor -- Constructor
---@param unit? TinkrObjectReference ---@param unit? TinkrObjectReference
---@return Bastion.Unit
function Unit:New(unit) function Unit:New(unit)
---@class Bastion.Unit ---@class Bastion.Unit
local self = setmetatable({}, Unit) local self = setmetatable({}, Unit)
@ -68,6 +84,7 @@ function Unit:New(unit)
self.cache = Bastion.Cache:New() self.cache = Bastion.Cache:New()
self.aura_table = Bastion.AuraTable:New(self) self.aura_table = Bastion.AuraTable:New(self)
self.regression_history = {} self.regression_history = {}
self.health = {}
return self return self
end end
@ -78,12 +95,12 @@ function Unit:IsValid()
end end
-- Check if the unit exists -- Check if the unit exists
---@return boolean
function Unit:Exists() function Unit:Exists()
return Object(self:GetOMToken()) ~= false return Object(self:GetOMToken()) ~= false
end end
-- Get the units token -- Get the units token
---@return string
function Unit:Token() function Unit:Token()
return self:GetOMToken() return self:GetOMToken()
end end
@ -180,7 +197,6 @@ function Unit:GetPowerDeficit(powerType)
end end
-- Get the units position -- Get the units position
---@return Bastion.Vector3
function Unit:GetPosition() function Unit:GetPosition()
local x, y, z = ObjectPosition(self:GetOMToken()) local x, y, z = ObjectPosition(self:GetOMToken())
return Bastion.Vector3:New(x, y, z) return Bastion.Vector3:New(x, y, z)
@ -311,11 +327,12 @@ function Unit:IsDamage()
end end
-- Get the units role -- Get the units role
---@return "TANK" | "HEALER" | "DAMAGER" ---@return "TANK" | "HEALER" | "DAMAGER" | "NONE"
function Unit:GetRole() function Unit:GetRole()
return UnitGroupRolesAssigned(self:GetOMToken()) return UnitGroupRolesAssigned(self:GetOMToken())
end end
---@return number | boolean
function Unit:GetSpecializationID() function Unit:GetSpecializationID()
if CanInspect(self:GetOMToken(), false) then if CanInspect(self:GetOMToken(), false) then
return ObjectSpecializationID(self:GetOMToken()) return ObjectSpecializationID(self:GetOMToken())
@ -324,10 +341,10 @@ function Unit:GetSpecializationID()
end end
---@param fallback? boolean ---@param fallback? boolean
---@return "TANK" | "HEALER" | "DAMAGER" | false ---@return "TANK" | "HEALER" | "DAMAGER" | "NONE" | false
function Unit:GetSpecializationRole(fallback) function Unit:GetSpecializationRole(fallback)
local specID = self:GetSpecializationID() local specID = self:GetSpecializationID()
if specID then if type(specID) == "number" then
return GetSpecializationRoleByID(specID) return GetSpecializationRoleByID(specID)
end end
return fallback and self:GetRole() or false return fallback and self:GetRole() or false
@ -398,6 +415,15 @@ function Unit:CanSee(unit)
-- return false -- return false
-- end -- end
-- 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 ax, ay, az = ObjectPosition(self:GetOMToken())
local ah = ObjectHeight(self:GetOMToken()) local ah = ObjectHeight(self:GetOMToken())
local attx, atty, attz = GetUnitAttachmentPosition(unit:GetOMToken(), 34) local attx, atty, attz = GetUnitAttachmentPosition(unit:GetOMToken(), 34)
@ -432,6 +458,8 @@ function Unit:IsCasting()
return UnitCastingInfo(self:GetOMToken()) ~= nil return UnitCastingInfo(self:GetOMToken()) ~= nil
end end
---@param percent number
---@return number
function Unit:GetTimeCastIsAt(percent) function Unit:GetTimeCastIsAt(percent)
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken()) self:GetOMToken())
@ -453,7 +481,7 @@ function Unit:GetTimeCastIsAt(percent)
end end
-- Get Casting or channeling spell -- Get Casting or channeling spell
---@return Bastion.Spell | nil ---@return Bastion.Spell | boolean
function Unit:GetCastingOrChannelingSpell() function Unit:GetCastingOrChannelingSpell()
local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo( local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId = UnitCastingInfo(
self:GetOMToken()) self:GetOMToken())
@ -466,8 +494,7 @@ function Unit:GetCastingOrChannelingSpell()
if name then if name then
return Bastion.Globals.SpellBook:GetSpell(spellId) return Bastion.Globals.SpellBook:GetSpell(spellId)
end end
return false
return nil
end end
-- Get the end time of the cast or channel -- Get the end time of the cast or channel
@ -500,6 +527,7 @@ function Unit:IsCastingOrChanneling()
return self:IsCasting() or self:IsChanneling() return self:IsCasting() or self:IsChanneling()
end end
---@return Bastion.Unit
function Unit:CastTarget() function Unit:CastTarget()
---@diagnostic disable-next-line: param-type-mismatch ---@diagnostic disable-next-line: param-type-mismatch
return self:IsCastingOrChanneling() and Bastion.UnitManager:Get(ObjectCastingTarget(self:GetOMToken())) or return self:IsCastingOrChanneling() and Bastion.UnitManager:Get(ObjectCastingTarget(self:GetOMToken())) or
@ -507,10 +535,12 @@ function Unit:CastTarget()
end end
---@param unit Bastion.Unit ---@param unit Bastion.Unit
---@return boolean
function Unit:CastTargetIsUnit(unit) function Unit:CastTargetIsUnit(unit)
return self:IsCastingOrChanneling() and self:CastTarget():IsUnit(unit) return self:IsCastingOrChanneling() and self:CastTarget():IsUnit(unit)
end end
---@return boolean
function Unit:IsImmobilized() function Unit:IsImmobilized()
return bit.band(self:GetMovementFlag(), 0x400) > 0 return bit.band(self:GetMovementFlag(), 0x400) > 0
end end
@ -569,7 +599,7 @@ function Unit:IsInterruptibleAt(percent, ignoreInterruptible)
return false return false
end end
local percent = percent or math.random(2, 5) local percent = percent or math.random(2, 20)
local castPercent = self:GetChannelOrCastPercentComplete() local castPercent = self:GetChannelOrCastPercentComplete()
if castPercent >= percent then if castPercent >= percent then
@ -645,6 +675,7 @@ function Unit:IsMoving()
return GetUnitSpeed(self:GetOMToken()) > 0 return GetUnitSpeed(self:GetOMToken()) > 0
end end
---@return TinkrMovementFlags
function Unit:GetMovementFlag() function Unit:GetMovementFlag()
return ObjectMovementFlag(self:GetOMToken()) return ObjectMovementFlag(self:GetOMToken())
end end
@ -786,15 +817,15 @@ end
---@param unit Bastion.Unit ---@param unit Bastion.Unit
---@return boolean ---@return boolean
function Unit:InMelee(unit) function Unit:InMelee(unit)
local x, y, z = ObjectPosition(self.unit) local x, y, z = ObjectPosition(self:GetOMToken())
local x2, y2, z2 = ObjectPosition(unit.unit) local x2, y2, z2 = ObjectPosition(unit:GetOMToken())
if not x or not x2 then if not x or not x2 then
return false return false
end end
local scr = ObjectCombatReach(self.unit) local scr = ObjectCombatReach(self:GetOMToken())
local ucr = ObjectCombatReach(unit.unit) local ucr = ObjectCombatReach(unit:GetOMToken())
if not scr or not ucr then if not scr or not ucr then
return false return false
@ -808,6 +839,7 @@ function Unit:InMelee(unit)
end end
-- Get object id -- Get object id
---@return number
function Unit:GetID() function Unit:GetID()
if self.id ~= false then if self.id ~= false then
---@type number ---@type number
@ -830,6 +862,7 @@ function Unit:IsInRaid()
return UnitInRaid(self:GetOMToken()) ~= nil return UnitInRaid(self:GetOMToken()) ~= nil
end end
---@return boolean
function Unit:IsInPartyOrRaid() function Unit:IsInPartyOrRaid()
return self:IsInParty() or self:IsInRaid() return self:IsInParty() or self:IsInRaid()
end end
@ -874,7 +907,7 @@ function Unit:PredictHealth(time)
table.remove(self.regression_history, 1) table.remove(self.regression_history, 1)
end 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 for i = 1, #self.regression_history do
local entry = self.regression_history[i] local entry = self.regression_history[i]
@ -883,7 +916,7 @@ function Unit:PredictHealth(time)
end end
local slope, intercept = self:LinearRegression(x, y) local slope, intercept = self:LinearRegression(x, y)
return slope * time + intercept return slope * (Bastion.Now + time) + intercept
end end
-- Use linear regression to guess the time until a given health percent -- 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) table.remove(self.regression_history, 1)
end 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 for i = 1, #self.regression_history do
local entry = self.regression_history[i] local entry = self.regression_history[i]
@ -926,7 +959,7 @@ end
function Unit:TimeToDie() function Unit:TimeToDie()
if self:IsDead() then if self:IsDead() then
self.regression_history = {} self.regression_history = {}
if type(self.ttd_ticker) == "table" then if self.ttd_ticker then
self.ttd_ticker:Cancel() self.ttd_ticker:Cancel()
self.ttd_ticker = false self.ttd_ticker = false
end end
@ -955,6 +988,15 @@ function Unit:TimeToDie()
return self.ttd return self.ttd
end 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 -- Set combat time if affecting combat and return the difference between now and the last time
---@return number ---@return number
function Unit:GetCombatTime() function Unit:GetCombatTime()
@ -1053,37 +1095,39 @@ function Unit:GetSwingTimers()
return main_speed_remains, off_speed_remains return main_speed_remains, off_speed_remains
end end
---@return nil
function Unit:WatchForSwings() function Unit:WatchForSwings()
Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() if not self.watching_for_swings then
local _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function()
CombatLogGetCurrentEventInfo() 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 if sourceGUID == self:GetGUID() and subtype then
self.last_shadow_techniques = GetTime() if subtype == "SPELL_ENERGIZE" and spellID == 196911 then
self.swings_since_sht = 0 self.last_shadow_techniques = GetTime()
end self.swings_since_sht = 0
if subtype:sub(1, 5) == "SWING" and not multistrike then
if subtype == "SWING_MISSED" then
offhand = spellName
end 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 local now = GetTime()
self.swings_since_sht = self.swings_since_sht + 1
end
if offhand then if now > self.last_shadow_techniques + 3 then
self.last_off_attack = GetTime() self.swings_since_sht = self.swings_since_sht + 1
else end
self.last_main_attack = GetTime()
if offhand then
self.last_off_attack = GetTime()
else
self.last_main_attack = GetTime()
end
end end
end end
end end)
end) self.watching_for_swings = true
end
end end
-- ismounted -- ismounted
@ -1137,6 +1181,7 @@ function Unit:GetStaggerPercent()
end end
-- Get the units power regen rate -- Get the units power regen rate
---@return number, number
function Unit:GetPowerRegen() function Unit:GetPowerRegen()
---@diagnostic disable-next-line: redundant-parameter ---@diagnostic disable-next-line: redundant-parameter
return GetPowerRegen(self:GetOMToken()) return GetPowerRegen(self:GetOMToken())
@ -1226,6 +1271,7 @@ function Unit:IsWithinCone(Target, Angle, Distance, rotation)
return diff <= Angle and self:GetDistance(Target) <= Distance return diff <= Angle and self:GetDistance(Target) <= Distance
end end
---@return number
function Unit:GetEmpoweredStage() function Unit:GetEmpoweredStage()
local stage = 0 local stage = 0
local _, _, _, startTime, _, _, _, spellID, _, numStages = UnitChannelInfo(self:GetOMToken()) local _, _, _, startTime, _, _, _, spellID, _, numStages = UnitChannelInfo(self:GetOMToken())
@ -1245,26 +1291,33 @@ function Unit:GetEmpoweredStage()
return stage return stage
end end
---@return boolean
function Unit:IsConnected() function Unit:IsConnected()
return UnitIsConnected(self:GetOMToken()) return UnitIsConnected(self:GetOMToken())
end end
---@return boolean
function Unit:HasIncomingRessurection() function Unit:HasIncomingRessurection()
---@diagnostic disable-next-line: return-type-mismatch
return self:IsDead() and UnitHasIncomingResurrection(self:GetOMToken()) return self:IsDead() and UnitHasIncomingResurrection(self:GetOMToken())
end end
---@return WowGameObject | false
function Unit:LootTarget() function Unit:LootTarget()
return ObjectLootTarget(self:GetOMToken()) return ObjectLootTarget(self:GetOMToken())
end end
---@return boolean
function Unit:CanLoot() function Unit:CanLoot()
return ObjectLootable(self:GetOMToken()) return ObjectLootable(self:GetOMToken())
end end
---@return boolean
function Unit:HasTarget() function Unit:HasTarget()
return ObjectTarget(self:GetOMToken()) ~= false return ObjectTarget(self:GetOMToken()) ~= false
end end
---@return Bastion.Unit
function Unit:Target() function Unit:Target()
return self:HasTarget() and Bastion.UnitManager:Get(ObjectTarget(self:GetOMToken()):unit()) or return self:HasTarget() and Bastion.UnitManager:Get(ObjectTarget(self:GetOMToken()):unit()) or
Bastion.UnitManager:Get("none") Bastion.UnitManager:Get("none")
@ -1272,112 +1325,114 @@ end
local dummyUnits = { local dummyUnits = {
-- City (SW, Orgri, ...) -- City (SW, Orgri, ...)
[31146] = true, -- Raider's Training Dummy [31146] = true, -- Raider's Training Dummy
[31144] = true, -- Training Dummy [31144] = true, -- Training Dummy
[32666] = true, -- Training Dummy [32666] = true, -- Training Dummy
[32667] = true, -- Training Dummy [32667] = true, -- Training Dummy
[46647] = true, -- Training Dummy [46647] = true, -- Training Dummy
[114832] = true, -- PvP Training Dummy [114832] = true, -- PvP Training Dummy
[153285] = true, -- Training Dummy [153285] = true, -- Training Dummy
[153292] = true, -- Training Dummy [153292] = true, -- Training Dummy
-- MoP Shrine of Two Moons -- MoP Shrine of Two Moons
[67127] = true, -- Training Dummy [67127] = true, -- Training Dummy
-- WoD Alliance Garrison -- WoD Alliance Garrison
[87317] = true, -- Mage Tower Damage Training Dummy [87317] = true, -- Mage Tower Damage Training Dummy
[87318] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) [87318] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison)
[87320] = true, -- Mage Tower Damage Raider's Training Dummy [87320] = true, -- Mage Tower Damage Raider's Training Dummy
[88314] = true, -- Tanking Dungeoneer's Training Dummy [88314] = true, -- Tanking Dungeoneer's Training Dummy
[88316] = true, -- Healing Training Dummy ----> FRIENDLY [88316] = true, -- Healing Training Dummy ----> FRIENDLY
-- WoD Horde Garrison -- WoD Horde Garrison
[87760] = true, -- Mage Tower Damage Training Dummy [87760] = true, -- Mage Tower Damage Training Dummy
[87761] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison) [87761] = true, -- Mage Tower Damage Dungeoneer's Training Dummy (& Garrison)
[87762] = true, -- Mage Tower Damage Raider's Training Dummy [87762] = true, -- Mage Tower Damage Raider's Training Dummy
[88288] = true, -- Tanking Dungeoneer's Training Dummy [88288] = true, -- Tanking Dungeoneer's Training Dummy
[88289] = true, -- Healing Training Dummy ----> FRIENDLY [88289] = true, -- Healing Training Dummy ----> FRIENDLY
-- Legion Druid Class Order Hall -- Legion Druid Class Order Hall
[113964] = true, -- Raider's Training Dummy [113964] = true, -- Raider's Training Dummy
[113966] = true, -- Dungeoneer's Training Dummy [113966] = true, -- Dungeoneer's Training Dummy
-- Legion Mage Class Order Hall -- Legion Mage Class Order Hall
[103397] = true, -- Greater Bullwark Construct [103397] = true, -- Greater Bullwark Construct
[103404] = true, -- Bullwark Construct [103404] = true, -- Bullwark Construct
[103402] = true, -- Lesser Bullwark Construct [103402] = true, -- Lesser Bullwark Construct
-- Legion Priest Class Order Hall -- Legion Priest Class Order Hall
[107555] = true, -- Bound void Wraith [107555] = true, -- Bound void Wraith
[107556] = true, -- Bound void Walker [107556] = true, -- Bound void Walker
-- Legion Rogue Class Order Hall -- Legion Rogue Class Order Hall
[92164] = true, -- Training Dummy [92164] = true, -- Training Dummy
[92165] = true, -- Dungeoneer's Training Dummy [92165] = true, -- Dungeoneer's Training Dummy
[92166] = true, -- Raider's Training Dummy [92166] = true, -- Raider's Training Dummy
-- Legion Warlock Class Order Hall -- Legion Warlock Class Order Hall
[101956] = true, -- Rebellious Fel Lord [101956] = true, -- Rebellious Fel Lord
[102045] = true, -- Rebellious WrathGuard [102045] = true, -- Rebellious WrathGuard
[102048] = true, -- Rebellious Felguard [102048] = true, -- Rebellious Felguard
[102052] = true, -- Rebellious imp [102052] = true, -- Rebellious imp
-- BfA Dazar'Alor -- BfA Dazar'Alor
[144081] = true, -- Training Dummy [144081] = true, -- Training Dummy
[144082] = true, -- Training Dummy [144082] = true, -- Training Dummy
[144085] = true, -- Training Dummy [144085] = true, -- Training Dummy
[144086] = true, -- Raider's Training Dummy [144086] = true, -- Raider's Training Dummy
-- BfA Boralus -- BfA Boralus
[126781] = true, -- Training Dummy [126781] = true, -- Training Dummy
[131983] = true, -- Raider's Training Dummy [131983] = true, -- Raider's Training Dummy
[131989] = true, -- Training Dummy [131989] = true, -- Training Dummy
[131992] = true, -- Dungeoneer's Training Dummy [131992] = true, -- Dungeoneer's Training Dummy
-- Shadowlands Kyrian -- Shadowlands Kyrian
[154564] = true, -- Valiant's Humility [154564] = true, -- Valiant's Humility
[154567] = true, -- Purity's Cleaning [154567] = true, -- Purity's Cleaning
[154580] = true, -- Reinforced Guardian [154580] = true, -- Reinforced Guardian
[154583] = true, -- Starlwart Guardian [154583] = true, -- Starlwart Guardian
[154585] = true, -- Valiant's Resolve [154585] = true, -- Valiant's Resolve
[154586] = true, -- Stalwart Phalanx [154586] = true, -- Stalwart Phalanx
[160325] = true, -- Humility's Obedience [160325] = true, -- Humility's Obedience
-- Shadowlands Venthyr -- Shadowlands Venthyr
[173942] = true, -- Training Dummy [173942] = true, -- Training Dummy
[175449] = true, -- Raider's Training Dummy [175449] = true, -- Raider's Training Dummy
[175450] = true, -- Dungeoneer's Training Dummy [175450] = true, -- Dungeoneer's Training Dummy
[175451] = true, -- Dungeoneer's Tanking Dummy [175451] = true, -- Dungeoneer's Tanking Dummy
[175452] = true, -- Raider's Tanking Dummy [175452] = true, -- Raider's Tanking Dummy
[175455] = true, -- Cleave Training Dummy [175455] = true, -- Cleave Training Dummy
[175456] = true, -- Swarm Training Dummy [175456] = true, -- Swarm Training Dummy
[175462] = true, -- Sinfall Fiend [175462] = true, -- Sinfall Fiend
-- Shadowlands Night Fae -- Shadowlands Night Fae
[174565] = true, -- Dungeoneer's Tanking Dummy [174565] = true, -- Dungeoneer's Tanking Dummy
[174566] = true, -- Raider's Tanking Dummy [174566] = true, -- Raider's Tanking Dummy
[174567] = true, -- Raider's Training Dummy [174567] = true, -- Raider's Training Dummy
[174568] = true, -- Dungeoneer's Training Dummy [174568] = true, -- Dungeoneer's Training Dummy
[174569] = true, -- Training Dummy [174569] = true, -- Training Dummy
[174570] = true, -- Swarm Training Dummy [174570] = true, -- Swarm Training Dummy
[174571] = true, -- Cleave Training Dummy [174571] = true, -- Cleave Training Dummy
-- Shadowlands Necrolord -- Shadowlands Necrolord
[174484] = true, -- Dungeoneer's Training Dummy [174484] = true, -- Dungeoneer's Training Dummy
[174487] = true, -- Training Dummy [174487] = true, -- Training Dummy
[174488] = true, -- Raider's Training Dummy [174488] = true, -- Raider's Training Dummy
[174491] = true, -- Tanking Dummy [174491] = true, -- Tanking Dummy
-- DargonFlight Valdrakken -- DargonFlight Valdrakken
[198594] = true, -- Cleave Training Dummy [198594] = true, -- Cleave Training Dummy
[194648] = true, -- Training Dummy [194648] = false, -- Training Dummy
[189632] = true, -- Animated Duelist [189632] = true, -- Animated Duelist
[194643] = true, -- Dungeoneer's Training Dummy [194643] = true, -- Dungeoneer's Training Dummy
[194644] = true, -- Dungeoneer's Training Dummy [194644] = true, -- Dungeoneer's Training Dummy
[197833] = true, -- PvP Training Dummy [197833] = true, -- PvP Training Dummy
[189617] = true, -- Boulderfist [189617] = true, -- Boulderfist
[194649] = true, -- Normal Tank Dummy [194649] = true, -- Normal Tank Dummy
-- DargonFlight Iskaara -- DargonFlight Iskaara
[193563] = true, -- Training Dummy [193563] = true, -- Training Dummy
-- Other -- Other
[65310] = true, -- Turnip Punching Bag (toy) [65310] = true, -- Turnip Punching Bag (toy)
[66374] = true, -- Anatomical Dummy (toy) [66374] = true, -- Anatomical Dummy (toy)
[196394] = true, -- Tuskarr Training Dummy (toy) [196394] = true, -- Tuskarr Training Dummy (toy)
[196406] = true, -- Rubbery Fish Head (toy) [196406] = true, -- Rubbery Fish Head (toy)
[199057] = true, -- Black Dragon's Challenge Dummy (toy) [199057] = true, -- Black Dragon's Challenge Dummy (toy)
} }
---@return boolean
function Unit:IsDummy() function Unit:IsDummy()
local npcId = self:GetID() local npcId = self:GetID()
return npcId and npcId >= 0 and dummyUnits[npcId] == true or false return npcId and npcId >= 0 and dummyUnits[npcId] == true or false
end end
---@param npcId? number ---@param npcId? number
---@return boolean
function Unit:IsInBossList(npcId) function Unit:IsInBossList(npcId)
npcId = npcId or self:GetID() npcId = npcId or self:GetID()
for i = 1, 4 do for i = 1, 4 do
@ -1386,9 +1441,11 @@ function Unit:IsInBossList(npcId)
return true return true
end end
end end
return false
end end
---@param npcId number ---@param npcId number
---@return number
function Unit:SpecialTTDPercentage(npcId) function Unit:SpecialTTDPercentage(npcId)
local specialTTDPercentage = Bastion.TimeToDie.specialTTDPercentageData[npcId] local specialTTDPercentage = Bastion.TimeToDie.specialTTDPercentageData[npcId]
if not specialTTDPercentage then return 0 end if not specialTTDPercentage then return 0 end
@ -1402,6 +1459,7 @@ end
---@param npcId? number ---@param npcId? number
---@param hp? number ---@param hp? number
---@return boolean
function Unit:CheckHPFromBossList(npcId, hp) function Unit:CheckHPFromBossList(npcId, hp)
npcId = npcId or self:GetID() npcId = npcId or self:GetID()
local thisHP = hp or 100 local thisHP = hp or 100
@ -1415,13 +1473,16 @@ function Unit:CheckHPFromBossList(npcId, hp)
return false return false
end end
---@param percentage number
---@param minSamples? number
---@return Bastion.TimeToDie.Enums | integer
function Unit:TimeToX(percentage, minSamples) function Unit:TimeToX(percentage, minSamples)
if self:IsDummy() then return 6666 end --if self:IsDummy() then return 6666 end
if self:IsPlayer() and Bastion.UnitManager:Get("player"):CanAttack(self) then return 25 end if self:IsPlayer() and Bastion.UnitManager:Get("player"):CanAttack(self) then return Bastion.TimeToDie.Enums.PLAYER end
local seconds = 8888 local seconds = 0
local unitGuid = self:GetGUID() local unitGuid = self:GetGUID()
if not unitGuid then if not unitGuid then
return seconds return Bastion.TimeToDie.Enums.NO_GUID
end end
local unitTable = Bastion.TimeToDie.Units[unitGuid] local unitTable = Bastion.TimeToDie.Units[unitGuid]
-- Simple linear regression -- Simple linear regression
@ -1456,8 +1517,8 @@ function Unit:TimeToX(percentage, minSamples)
-- Use best fit line to calculate estimated time to reach target health -- Use best fit line to calculate estimated time to reach target health
seconds = (percentage - a) / b seconds = (percentage - a) / b
-- Subtract current time to obtain "time remaining" -- Subtract current time to obtain "time remaining"
seconds = math.min(7777, seconds - (GetTime() - unitTable[2])) seconds = seconds - (GetTime() - unitTable[2])
if seconds < 0 then seconds = 9999 end if seconds < 0 then seconds = Bastion.TimeToDie.Enums.NEGATIVE_TTD end
end end
end end
end end
@ -1465,12 +1526,15 @@ function Unit:TimeToX(percentage, minSamples)
end end
---@param minSamples? number ---@param minSamples? number
---@return Bastion.TimeToDie.Enums | integer
function Unit:TimeToDie2(minSamples) function Unit:TimeToDie2(minSamples)
if not self:Exists() then if not self:Exists() then
return 11111 return Bastion.TimeToDie.Enums.DOES_NOT_EXIST
end end
local unitGuid = self:GetGUID() 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 minSamples = minSamples or 3
---@type {TTD: {[number]: number}} ---@type {TTD: {[number]: number}}
@ -1482,23 +1546,28 @@ function Unit:TimeToDie2(minSamples)
ttd = {} ttd = {}
unitInfo.TTD = ttd unitInfo.TTD = ttd
end end
local v
if not ttd[minSamples] then 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 end
Bastion.Globals.UnitInfo:Set(unitGuid, unitInfo, .5)
return ttd[minSamples] return ttd[minSamples] or v
end end
-- Get the boss unit TimeToDie -- Get the boss unit TimeToDie
---@param minSamples? number ---@param minSamples? number
---@return Bastion.TimeToDie.Enums | integer
function Unit:BossTimeToDie(minSamples) function Unit:BossTimeToDie(minSamples)
if self:IsInBossList() or self:IsDummy() then if self:IsInBossList() or self:IsDummy() then
return self:TimeToDie2(minSamples) return self:TimeToDie2(minSamples)
end end
return 11111 return Bastion.TimeToDie.Enums.DOES_NOT_EXIST
end end
-- Get if the unit meets the TimeToDie requirements. -- Get if the unit meets the TimeToDie requirements.
@ -1507,10 +1576,11 @@ end
---@param offset number ---@param offset number
---@param valueThreshold number ---@param valueThreshold number
---@param minSamples? number ---@param minSamples? number
---@return boolean
function Unit:FilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) function Unit:FilteredTimeToDie(operator, value, offset, valueThreshold, minSamples)
local TTD = self:TimeToDie2(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 end
-- Get if the boss unit meets the TimeToDie requirements. -- Get if the boss unit meets the TimeToDie requirements.
@ -1519,6 +1589,7 @@ end
---@param offset number ---@param offset number
---@param valueThreshold number ---@param valueThreshold number
---@param minSamples? number ---@param minSamples? number
---@return boolean
function Unit:BossFilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) function Unit:BossFilteredTimeToDie(operator, value, offset, valueThreshold, minSamples)
if self:IsInBossList() or self:IsDummy() then if self:IsInBossList() or self:IsDummy() then
return self:FilteredTimeToDie(operator, value, offset, valueThreshold, minSamples) 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). -- Get if the Time To Die is Valid for an Unit (i.e. not returning a warning code).
---@param minSamples? number ---@param minSamples? number
---@return boolean
function Unit:TimeToDieIsNotValid(minSamples) function Unit:TimeToDieIsNotValid(minSamples)
return self:TimeToDie2(minSamples) >= 7777 return self:TimeToDie2(minSamples) > -1
end 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). -- 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 ---@param minSamples? number
---@return boolean
function Unit:BossTimeToDieIsNotValid(minSamples) function Unit:BossTimeToDieIsNotValid(minSamples)
if self:IsInBossList() then if self:IsInBossList() then
return self:TimeToDieIsNotValid(minSamples) return self:TimeToDieIsNotValid(minSamples)
@ -1543,62 +1616,4 @@ function Unit:BossTimeToDieIsNotValid(minSamples)
return true return true
end end
-- local empowering = {}
-- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_START", function(...)
-- local unit, unk, id = ...
-- if not unit then return end
-- local guid = ObjectGUID(unit)
-- if not guid then return end
-- empowering[guid] = -1
-- end)
-- Bastion.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_EMPOWER_STOP", function(...)
-- local unit, unk, id = ...
-- if not unit then return end
-- local guid = ObjectGUID(unit)
-- if not guid then return end
-- empowering[guid] = -1
-- end)
-- function Unit:GetUnitEmpowerStage()
-- local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages =
-- UnitChannelInfo(self:GetOMToken());
-- if name and empowering[self:GetGUID()] == -1 then
-- empowering[self:GetGUID()] = numStages
-- end
-- if not name and empowering[self:GetGUID()] then
-- return empowering[self:GetGUID()]
-- end
-- if not name then
-- return empowering[self:GetGUID()]
-- end
-- local getStageDuration = function(stage)
-- if stage == numStages then
-- return GetUnitEmpowerHoldAtMaxTime(self:GetOMToken());
-- else
-- return GetUnitEmpowerStageDuration(self:GetOMToken(), stage - 1);
-- end
-- end
-- local time = GetTime() - (startTime / 1000);
-- local higheststage = 0
-- local sumdur = 0
-- for i = 1, numStages - 1, 1 do
-- local duration = getStageDuration(i) / 1000;
-- sumdur = sumdur + duration
-- if time > sumdur then
-- higheststage = i
-- end
-- end
-- return higheststage
-- end
return Unit return Unit

@ -11,7 +11,7 @@ local Unit = Bastion.Unit
---@class Bastion.UnitManager ---@class Bastion.UnitManager
---@field units table<string, Bastion.Unit> ---@field units table<string, Bastion.Unit>
---@field customUnits table<string, Bastion.UnitManager.CustomUnit> ---@field customUnits table<string, Bastion.UnitManager.CustomUnit>
---@field objects table<string, Bastion.Unit> ---@field objects table<string | WowGameObject, Bastion.Unit>
---@field cache Bastion.Cache ---@field cache Bastion.Cache
local UnitManager = { local UnitManager = {
units = {}, units = {},
@ -29,7 +29,7 @@ function UnitManager:__index(k)
return UnitManager[k] return UnitManager[k]
end 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 custom unit exists, return it it's cache expired return a new one
if self.customUnits[k] then if self.customUnits[k] then
@ -109,7 +109,7 @@ end
-- Get a unit by guid -- Get a unit by guid
---@param guid string | WowGameObject ---@param guid string | WowGameObject
---@return Bastion.Unit ---@return Bastion.Unit?
function UnitManager:GetObject(guid) function UnitManager:GetObject(guid)
return self.objects[guid] return self.objects[guid]
end end

@ -323,7 +323,7 @@ end
---@return Bastion.Vector3 ---@return Bastion.Vector3
function Vector3:Project(onNormal) function Vector3:Project(onNormal)
local num = onNormal:Dot(onNormal) local num = onNormal:Dot(onNormal)
if num < 1.401298E-45 then if num < 1.401298e-45 then
return Vector3:New(0, 0, 0) return Vector3:New(0, 0, 0)
end end

@ -111,9 +111,8 @@ local exampleNames = {
"ExampleModule.lua", "ExampleModule.lua",
} }
---@param dir string
local function Load(dir) local function Load(dir)
local dir = dir
if dir:sub(1, 1) == "@" then if dir:sub(1, 1) == "@" then
dir = dir:sub(2) dir = dir:sub(2)
dir = string.format("%s/%s", BastionScriptsBase, dir) dir = string.format("%s/%s", BastionScriptsBase, dir)
@ -205,6 +204,11 @@ Bastion.Notifications = Bastion.NotificationList:New()
Bastion.Config = Bastion.require("Bastion.Config") Bastion.Config = Bastion.require("Bastion.Config")
Bastion.TimeToDie = Bastion.require("Bastion.TimeToDie") 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 ---@enum (key) CompareThisTable
local compareThisTable = { local compareThisTable = {
@ -227,12 +231,16 @@ Bastion.Utils = {
end end
} }
---@type table<string, Bastion.Library>
local LIBRARIES = {} local LIBRARIES = {}
---@type Bastion.Module[] ---@type Bastion.Module[]
local MODULES = {} local MODULES = {}
Bastion.Enabled = false Bastion.Enabled = false
---@param unit UnitToken
---@param auras UnitAuraUpdateInfo
Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras)
---@type Bastion.Unit | nil ---@type Bastion.Unit | nil
local u = Bastion.UnitManager:Get(unit) local u = Bastion.UnitManager:Get(unit)
@ -243,6 +251,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras)
end) end)
Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...)
---@type UnitIds, string, spellId
local unit, castGUID, spellID = ... local unit, castGUID, spellID = ...
local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID) local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID)
@ -255,7 +264,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", functi
end end
end) end)
local pguid = UnitGUID("player") local playerGuid = UnitGUID("player")
local missed = {} local missed = {}
---@class Bastion.Globals.SpellName : { [spellId]: string } ---@class Bastion.Globals.SpellName : { [spellId]: string }
@ -303,7 +312,7 @@ Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", fun
if u2 then if u2 then
u2:SetLastCombatTime(t) 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] local missType = args[15]
if missType == "IMMUNE" then if missType == "IMMUNE" then
@ -318,10 +327,18 @@ Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", fun
end end
end end
end) end)
local Timer = { local Timer = {
TTD = 0 TTD = 0
} }
Bastion.Now = GetTime()
Bastion.Tick = GetGameTick()
Bastion.Ticker = C_Timer.NewTicker(0.1, function() Bastion.Ticker = C_Timer.NewTicker(0.1, function()
Bastion.Now = GetTime()
Bastion.Tick = GetGameTick()
if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then
Bastion.CombatTimer:Start() Bastion.CombatTimer:Start()
elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then elseif Bastion.CombatTimer:IsRunning() and not UnitAffectingCombat("player") then
@ -330,8 +347,8 @@ Bastion.Ticker = C_Timer.NewTicker(0.1, function()
if Bastion.Enabled then if Bastion.Enabled then
Bastion.ObjectManager:Refresh() Bastion.ObjectManager:Refresh()
if GetTime() > Timer.TTD then if Bastion.Now > Timer.TTD then
Timer.TTD = GetTime() + Bastion.TimeToDie.Settings.Refresh Timer.TTD = Bastion.Now + Bastion.TimeToDie.Settings.Refresh
Bastion.TimeToDie:Refresh() Bastion.TimeToDie:Refresh()
end end
for i = 1, #MODULES do for i = 1, #MODULES do
@ -340,6 +357,7 @@ Bastion.Ticker = C_Timer.NewTicker(0.1, function()
end end
end) end)
---@param module Bastion.Module
function Bastion:Register(module) function Bastion:Register(module)
table.insert(MODULES, module) table.insert(MODULES, module)
Bastion:Print("Registered", module) Bastion:Print("Registered", module)
@ -352,11 +370,10 @@ function Bastion:FindModule(name)
return MODULES[i] return MODULES[i]
end end
end end
return nil
end end
Bastion.PrintEnabled = false Bastion.PrintEnabled = false
function Bastion:Print(...) function Bastion:Print(...)
if not Bastion.PrintEnabled then if not Bastion.PrintEnabled then
return return
@ -494,6 +511,7 @@ function Bastion:CheckLibraryDependencies()
return true return true
end end
---@param library string
function Bastion:Import(library) function Bastion:Import(library)
local lib = self:GetLibrary(library) local lib = self:GetLibrary(library)
@ -504,6 +522,7 @@ function Bastion:Import(library)
return lib:Resolve() return lib:Resolve()
end end
---@param name string
function Bastion:GetLibrary(name) function Bastion:GetLibrary(name)
if not LIBRARIES[name] then if not LIBRARIES[name] then
error("Library " .. name .. " not found") error("Library " .. name .. " not found")

Loading…
Cancel
Save