main
jeffi 8 months ago
parent cd029208e3
commit 40f77a11e4
  1. 96
      src/APL/APL.lua
  2. 47
      src/APLActor/APLActor.lua
  3. 56
      src/APLActor/APLActor2.lua
  4. 6
      src/APLSpellActor/APLSpellActor.lua
  5. 120
      src/Bastion/Bastion.lua
  6. 2
      src/Cache/Cache.lua
  7. 61
      src/Missile/Missile.lua
  8. 137
      src/MissileManager/MissileManager.lua
  9. 15
      src/ObjectManager/ObjectManager.lua
  10. 51
      src/Spell/Spell.lua
  11. 22
      src/TimeToDie/TimeToDie.lua
  12. 150
      src/Unit/Unit.lua
  13. 44
      src/UnitManager/UnitManager.lua

@ -7,8 +7,7 @@ Bastion = ...
---@class Bastion.APL
---@field apl Bastion.APLActor[]
---@field variables table<string, any>
---@field name string
---@field last { successful: { name: string, time: number, index: number }, attempted: { name: string, time: number, index: number } }
---@field last { attempt: Bastion.APLActor, success: Bastion.APLActor, time: number, index: number }
local APL = {}
APL.__index = APL
@ -16,23 +15,19 @@ APL.__index = APL
---@param name string
---@return Bastion.APL
function APL:New(name)
local self = setmetatable({}, APL)
self.apl = {}
self.variables = {}
self.name = name
self.last = {
successful = {
name = "",
---@class Bastion.APL
local self = setmetatable({
active = false,
apl = {},
variables = {},
name = name,
last = {
success = {},
attempt = {},
time = -1,
index = -1,
},
attempted = {
name = "",
time = -1,
index = -1,
},
}
}, APL)
return self
end
@ -54,7 +49,6 @@ end
---@class Bastion.APL.Actor.Variable.Table : Bastion.APLActor.Table.Base
---@field variable string
---@field cb fun(...):any
---@field _apl Bastion.APL
-- Add variable
---@param name string
@ -65,7 +59,7 @@ function APL:AddVariable(name, cb)
type = "variable",
variable = name,
cb = cb,
_apl = self,
parentAPL = self,
})
table.insert(self.apl, actor)
return actor
@ -74,16 +68,21 @@ end
---@class Bastion.APL.Actor.Action.Table : Bastion.APLActor.Table.Base
---@field action string
---@field cb fun(...):any
---@field args? any[]
-- Add a manual action to the APL
---@generic A
---@param action string
---@param cb fun(...):any
---@param cb fun(...: A):any
---@param ... A
---@return Bastion.APLActor
function APL:AddAction(action, cb)
function APL:AddAction(action, cb, ...)
local actor = Bastion.APLActor:New({
type = "action",
action = action,
cb = cb,
parentAPL = self,
args = SafePack(...),
})
table.insert(self.apl, actor)
return actor
@ -91,16 +90,18 @@ end
---@class Bastion.APL.Actor.Spell.Table : Bastion.APLActor.Table.Base
---@field spell Bastion.Spell
---@field condition? string|fun(self: Bastion.Spell): boolean
---@field condition? string|fun(self: Bastion.Spell, target: Bastion.Unit|false): boolean
---@field castableFunc false | fun(self: Bastion.Spell): boolean
---@field target Bastion.Unit | false
---@field onCastFunc false | fun(self: Bastion.Spell):any
---@field targetIf fun(self: Bastion.Spell): (Bastion.Unit) | false
-- Add a spell to the APL
---@param spell Bastion.Spell
---@param condition? string|fun(self: Bastion.Spell):boolean
---@param condition? string|fun(self: Bastion.Spell, target: Bastion.Unit|false):boolean
---@param targetIf? fun(self: Bastion.Spell): Bastion.Unit
---@return Bastion.APLActor
function APL:AddSpell(spell, condition)
function APL:AddSpell(spell, condition, targetIf)
local castableFunc = spell.CastableIfFunc
local onCastFunc = spell.OnCastFunc
local target = spell:GetTarget()
@ -112,6 +113,15 @@ function APL:AddSpell(spell, condition)
castableFunc = castableFunc,
target = target,
onCastFunc = onCastFunc,
parentAPL = self,
targetIf = targetIf or false,
executor = function(actor)
local actorTable = actor.actor --[[@as Bastion.APL.Actor.Spell.Table]]
local spellTarget = actorTable.targetIf and actorTable.targetIf(actorTable.spell) or actorTable.target
return (not spellTarget or spellTarget:Exists())
and (not actorTable.condition or actorTable.condition(actorTable.spell, spellTarget))
and actorTable.spell:CastableIf(actorTable.castableFunc):OnCast(actorTable.onCastFunc):Cast(spellTarget)
end
})
table.insert(self.apl, actor)
@ -139,6 +149,7 @@ function APL:AddItem(item, condition)
condition = condition,
usableFunc = usableFunc,
target = target,
parentAPL = self,
})
table.insert(self.apl, actor)
@ -159,6 +170,7 @@ function APL:AddAPL(apl, condition)
type = "apl",
apl = apl,
condition = condition,
parentAPL = self,
})
table.insert(self.apl, actor)
return actor
@ -175,27 +187,42 @@ function APL:ToggleAPL(name, enabled)
end
end
function APL:UpdateLastAttempted(name, index)
self.last.attempted.name = name
self.last.attempted.time = GetTime()
self.last.attempted.index = index
---@param actor Bastion.APLActor
function APL:UpdateLastAttempted(actor)
self.active = true
self.last.time = GetTime()
self.last.attempt = actor
self.last.index = actor.index
end
function APL:UpdateLastSuccessful()
self.last.success = self.last.attempt
end
function APL:GetCurrentActor()
if self.active then
return self.last.attempt
else
return false
end
end
function APL:UpdateLastSuccessful(name, index)
self.last.successful.name = name
self.last.successful.time = GetTime()
self.last.successful.index = index
---@param active boolean
function APL:SetActive(active)
self.active = active
end
-- Execute the APL
function APL:Execute()
for i, actor in ipairs(self.apl) do
self:UpdateLastAttempted(actor.name, i)
self:UpdateLastAttempted(actor)
if actor.enabled and (not actor:HasTraits() or actor:Evaluate()) and actor:Execute() then
self:UpdateLastSuccessful(actor.name, i)
self:UpdateLastSuccessful()
return true
end
end
self.active = false
return false
end
---@class Bastion.APL.Actor.Sequencer.Table : Bastion.APLActor.Table.Base
@ -211,6 +238,7 @@ function APL:AddSequence(sequencer, condition)
type = "sequencer",
sequencer = sequencer,
condition = condition,
parentAPL = self,
})
table.insert(self.apl, actor)
return actor
@ -219,7 +247,7 @@ end
-- tostring
---@return string
function APL:__tostring()
return "Bastion.__APL(" .. self.name .. ")"
return string.format("APL(%s)", self.name)
end
Bastion.APL = APL

@ -6,6 +6,9 @@ Bastion = ...
---@class Bastion.APLActor.Table.Base
---@field type "spell" | "item" | "apl" | "sequencer" | "variable" | "action"
---@field name string
---@field parentAPL Bastion.APL
---@field index number
---@field executor fun(self: Bastion.APLActor):boolean
---@alias Bastion.APLActor.Table Bastion.APL.Actor.Spell.Table | Bastion.APL.Actor.Item.Table | Bastion.APL.Actor.APL.Table | Bastion.APL.Actor.Sequencer.Table | Bastion.APL.Actor.Variable.Table | Bastion.APL.Actor.Action.Table
@ -21,25 +24,28 @@ APLActor.__index = APLActor
-- Constructor
---@param actor Bastion.APLActor.Table
function APLActor:New(actor)
local self = setmetatable({}, APLActor)
---@class Bastion.APLActor
local self = setmetatable({
index = #actor.parentAPL.apl + 1,
}, APLActor)
if actor.type == "spell" then
local name = actor.spell:GetName() or "Unknown"
local id = actor.spell:GetID() or 0
self.name = string.format("[%s] `%s`<%s>", "spell", name, id)
self.name = string.format("<SPELL>%s[%s]", name, id)
elseif actor.type == "item" then
local name = actor.item:GetName() or "Unknown"
local id = actor.item:GetID() or 0
self.name = string.format("[%s] `%s`<%s>", "item", name, id)
self.name = string.format("<ITEM>%s[%s]", name, id)
elseif actor.type == "apl" then
self.name = string.format("[%s] `%s`", "apl", actor.apl.name or "Unknown")
self.name = string.format("<APL>%s", actor.apl.name or "Unknown")
elseif actor.type == "sequencer" then
self.name = string.format("[%s]", "sequencer")
self.name = string.format("<SEQUENCER>")
elseif actor.type == "variable" then
self.name = string.format("[%s] `%s`", "variable", actor.variable or "Unknown")
self.name = string.format("<VARIABLE>%s", actor.variable or "Unknown")
elseif actor.type == "action" then
self.name = string.format("[%s] `%s`", "action", actor.action or "Unknown")
self.name = string.format("<ACTION>%s", actor.action or "Unknown")
else
self.name = string.format("[%s] Unknown", actor.type or "Unknown")
self.name = string.format("<%s>", actor.type or "UNKNOWN")
end
self.actor = actor
self.enabled = true
@ -61,7 +67,7 @@ end
-- Get the actor
---@return Bastion.APLActor.Table
function APLActor:GetActor()
function APLActor:GetActorTable()
return self.actor
end
@ -69,7 +75,7 @@ end
---@return boolean
function APLActor:Evaluate()
for _, trait in ipairs(self.traits) do
if not trait:Evaluate(self:GetActor()) then
if not trait:Evaluate(self:GetActorTable()) then
return false
end
end
@ -77,9 +83,13 @@ function APLActor:Evaluate()
return true
end
function APLActor:ExecuteActor()
return self:GetActorTable().executor(self)
end
-- Execute
function APLActor:Execute()
local actorTable = self:GetActor()
local actorTable = self:GetActorTable()
-- 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
@ -92,6 +102,7 @@ function APLActor:Execute()
if actorTable.sequencer:ShouldReset() then
actorTable.sequencer:Reset()
end
return false
end
if actorTable.type == "apl" then
---@cast actorTable Bastion.APL.Actor.APL.Table
@ -101,23 +112,21 @@ function APLActor:Execute()
end
end
if actorTable.type == "spell" then
---@cast actorTable Bastion.APL.Actor.Spell.Table
return actorTable.spell:CastableIf(actorTable.castableFunc):OnCast(actorTable.onCastFunc):Cast(actorTable.target,
actorTable.condition)
return self:ExecuteActor()
end
if actorTable.type == "item" then
---@cast actorTable Bastion.APL.Actor.Item.Table
return actorTable.item:UsableIf(actorTable.usableFunc):Use(actorTable.target, actorTable.condition)
return ((not actorTable.target or actorTable.target:Exists()) and actorTable.item:UsableIf(actorTable.usableFunc):Use(actorTable.target, actorTable.condition))
end
if self:GetActor().type == "action" then
if self:GetActorTable().type == "action" then
---@cast actorTable Bastion.APL.Actor.Action.Table
-- print("Bastion: APL:Execute: Executing action " .. actorTable.action)
self:GetActor().cb()
self:GetActorTable().cb(SafeUnpack(actorTable.args))
end
if actorTable.type == "variable" then
---@cast actorTable Bastion.APL.Actor.Variable.Table
-- print("Bastion: APL:Execute: Setting variable " .. actorTable.variable)
actorTable._apl.variables[actorTable.variable] = actorTable.cb(actorTable._apl)
actorTable.parentAPL.variables[actorTable.variable] = actorTable.cb(actorTable.parentAPL)
end
return false
end
@ -131,7 +140,7 @@ end
-- tostring
---@return string
function APLActor:__tostring()
return string.format("Bastion.__APLActor(%s)", self.name)
return string.format("APLActor(%s)", self.name)
end
Bastion.APLActor = APLActor

@ -0,0 +1,56 @@
---@type Tinkr
local Tinkr,
---@class Bastion
Bastion = ...
---@class Bastion.APLActor2.Table.Base
---@field parentAPL Bastion.APL
---@field index number
-- Create an APL actor for the APL class
---@class Bastion.APLActor2
---@field traits Bastion.APLTrait[]
---@field table Bastion.APLActor2.Table.Base
---@field executor fun(self: Bastion.APLActor2): boolean
local APLActor = {}
APLActor.__index = APLActor
---@param type string
function APLActor:New(type)
---@class Bastion.APLActor2
local self = setmetatable({
type = type,
enabled = true,
traits = {},
table = {},
executor = function() return false end
}, APLActor)
return self
end
---@param ... any
function APLActor:GetName(...)
local paramStrings = type(...) ~= "nil" and table.concat({ ... }, "-") or ""
return string.format("[%s]%s", self.type, paramStrings)
end
-- Add a trait to the APL actor
---@param ... Bastion.APLTrait
---@return Bastion.APLActor2
function APLActor:AddTraits(...)
for _, trait in ipairs({ ... }) do
table.insert(self.traits, trait)
end
return self
end
-- Get the actor
---@return Bastion.APLActor2.Table.Base
function APLActor:GetActorTable()
return self.table
end
function APLActor:SetExecutor(executor)
self.executor = executor
end

@ -0,0 +1,6 @@
---@type Tinkr
local Tinkr,
---@class Bastion
Bastion = ...
---@class Bastion.APL.SpellActor

@ -5,8 +5,9 @@ local Tinkr = ...
---@class Bastion
local Bastion = {
CombatEvents = {},
Enabled = false,
Interval = 0.01,
Interval = 0.15,
Globals = {
---@type Bastion.Globals.SpellName
SpellName = {}
@ -52,6 +53,8 @@ local bastionFiles = {
"~/src/Module/Module",
"~/src/UnitManager/UnitManager",
"~/src/ObjectManager/ObjectManager",
"~/src/Missile/Missile",
"~/src/MissileManager/MissileManager",
"~/src/Spell/Spell",
"~/src/SpellBook/SpellBook",
"~/src/Item/Item",
@ -184,16 +187,66 @@ function Bastion:Toggle(toggle)
Bastion.Enabled = type(toggle) ~= "nil" and toggle or not Bastion.Enabled
end
local combatEventSuffix = {
["_DAMAGE"] = true,
["_MISSED"] = true,
["_ABSORBED"] = true,
["_DRAIN"] = true,
["_LEECH"] = true,
["_INTERRUPT"] = true,
["_DISPEL"] = true,
["_DISPEL_FAILED"] = true,
["_STOLEN"] = true,
["_EXTRA_ATTACKS"] = true,
["_AURA_APPLIED"] = true,
["_AURA_REMOVED"] = true,
["_AURA_APPLIED_DOSE"] = true,
["_AURA_REMOVED_DOSE"] = true,
["_AURA_REFRESH"] = true,
["_AURA_BROKEN"] = true,
["_AURA_BROKEN_SPELL"] = true,
["_CAST_START"] = true,
["_CAST_SUCCESS"] = true,
["_CAST_FAILED"] = true,
["_INSTAKILL"] = true,
["_DURABILITY_DAMAGE"] = true,
["_DURABILITY_DAMAGE_ALL"] = true,
["_CREATE"] = true,
["_SUMMON"] = true,
["_RESURRECT"] = true,
["_EMPOWER_START"] = true,
["_EMPOWER_END"] = true,
["_EMPOWER_INTERRUPT"] = true,
}
local combatEventPrefix = {
"SWING",
"RANGE",
"SPELL",
"SPELL_PERIODIC",
"SPELL_BUILDING",
"ENVIRONMENTAL"
}
local loaded = false
function Bastion:Load()
if loaded then
return self
end
for suffix, _ in pairs(combatEventSuffix) do
for _, prefix in pairs(combatEventPrefix) do
Bastion.CombatEvents[prefix .. suffix] = true
end
end
for i = 1, #bastionFiles do
self:Require(bastionFiles[i])
end
self.Globals.EventManager:RegisterWoWEvent("PLAYER_ENTERING_WORLD", function()
self.UnitManager:ResetObjects()
end)
self.Globals.Command:Register('toggle', 'Toggle bastion on/off', function()
self:Toggle()
if self.Enabled then
@ -215,10 +268,9 @@ function Bastion:Load()
---@param unit UnitToken
---@param auras UnitAuraUpdateInfo
self.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras)
---@type Bastion.Unit | nil
local u = self.UnitManager:Get(unit)
if u then
if u:Exists() then
u:GetAuras():OnUpdate(auras)
end
end)
@ -240,36 +292,69 @@ function Bastion:Load()
local playerGuid = UnitGUID("player")
local missed = {}
self.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function()
Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_COMBAT",
---@param unitTarget UnitIds
---@param event string
---@param flagText string
---@param amount number
---@param schoolMask number
function(unitTarget, event, flagText, amount, schoolMask)
--[[ local unit = Bastion.UnitManager:Get(unitTarget)
if unit:IsAffectingCombat() then
unit:SetLastCombatTime()
end ]]
end)
Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function()
local args = { CombatLogGetCurrentEventInfo() }
---@type string
local subEvent = args[2]
---@type string
local sourceGUID = args[4]
---@type string
local destGUID = args[8]
---@type number
local spellID = args[12]
---@type string
local spellName = args[13]
if subEvent == "SPELL_CAST_SUCCESS" then
if (not self.Globals.SpellName[spellID] or self.Globals.SpellName[spellID] ~= spellName) then
self.Globals.SpellName[spellID] = spellName
if not Bastion.Globals.SpellName[spellID] then
Bastion.Globals.SpellName[spellID] = spellName
end
end
local u = self.UnitManager[sourceGUID]
local u2 = self.UnitManager[destGUID]
local t = GetTime()
local sourceUnit = Bastion.UnitManager:GetObject(sourceGUID)
local destUnit = Bastion.UnitManager:GetObject(destGUID)
--local t = GetTime()
if u then
u:SetLastCombatTime(t)
if sourceUnit and sourceUnit:IsAffectingCombat() then
sourceUnit:SetLastCombatTime()
end
if u2 then
u2:SetLastCombatTime(t)
local updateDestUnit = destUnit and destUnit:IsAffectingCombat()
if Bastion.CombatEvents[subEvent] and not updateDestUnit or ((sourceUnit and sourceUnit:IsValid() and destUnit and destUnit:IsValid()) and not UnitThreatSituation(sourceUnit.unit:unit() --[[@as string]], destUnit.unit:unit() --[[@as string]])) then
updateDestUnit = true
--[[ for key, val in pairs(combatEvents) do
if val and subEvent:find(key) then
end
end ]]
end
if destUnit and updateDestUnit then
destUnit:SetLastCombatTime()
end
--[[ if destUnit then
if subEvent == "SPELL_MISSED" and sourceGUID == playerGuid and spellID == 408 then
local missType = args[15]
if missType == "IMMUNE" then
local castingSpell = u:GetCastingOrChannelingSpell()
local castingSpell = sourceUnit:GetCastingOrChannelingSpell()
if castingSpell and type(castingSpell) == "table" then
if not missed[castingSpell:GetID()] then
@ -278,16 +363,19 @@ function Bastion:Load()
end
end
end
end
end ]]
end)
self.Ticker = C_Timer.NewTicker(self.Interval, function()
self.Globals.CombatTimer:Check()
if Bastion.Enabled then
if self.Enabled then
self.MissileManager:Refresh()
self.ObjectManager:Refresh()
self.TimeToDie:Refresh()
self:TickModules()
else
self.UnitManager:ResetObjects()
end
end)

@ -42,7 +42,7 @@ function Cache:Get(key)
return nil
end
---@param key any
---@param key string|number|table
---@return boolean
function Cache:IsCached(key)
self.cache = self.cache or {}

@ -0,0 +1,61 @@
---@type Tinkr
local Tinkr,
---@class Bastion
Bastion = ...
---@class Bastion.Missile : Tinkr.Missile
local Missile = {
}
function Missile:__index(k)
if k == "_missile" then
return rawget(self, k)
end
local response = rawget(self._missile, k)
return response ~= nil and response or rawget(self, k)
end
---@param missile Tinkr.Missile
function Missile:New(missile)
---@class Bastion.Missile
local self = setmetatable({
_missile = missile
}, Missile)
return self
end
function Missile:GetSourceUnit()
return Bastion.UnitManager:Get(self.source)
end
function Missile:GetTargetUnit()
return Bastion.UnitManager:Get(self.target)
end
function Missile:GetCurrentVector()
return Bastion.Vector3:New(self.cx, self.cy, self.cz)
end
function Missile:GetHitVector()
return Bastion.Vector3:New(self.hx, self.hy, self.hz)
end
function Missile:GetInitialVector()
return Bastion.Vector3:New(self.ix, self.iy, self.iz)
end
function Missile:GetModelVector()
return Bastion.Vector3:New(self.mx, self.my, self.mz)
end
function Missile:GetPVector()
return Bastion.Vector3:New(self.px, self.py, self.pz)
end
function Missile:GetUVector()
return Bastion.Vector3:New(self.ux, self.uy, self.uz)
end
Bastion.Missile = Missile

@ -0,0 +1,137 @@
---@type Tinkr
local Tinkr,
---@class Bastion
Bastion = ...
---@class Bastion.MissileManager.TrackingParams
---@field source? Bastion.Unit
---@field target? Bastion.Unit
---@field spellId? number
---@field spellVisualId? number
---@field callback fun(self: Bastion.Missile)
---@class Bastion.MissileManager
---@field _lists table<string, {list: Bastion.List, cb:fun(missile: Tinkr.Missile): any}>
---@field trackingParams Bastion.MissileManager.TrackingParams[]
---@field missiles Tinkr.Missile[]
local MissileManager = {}
MissileManager.__index = MissileManager
function MissileManager:New()
---@class Bastion.MissileManager
local self = setmetatable({}, MissileManager)
self.missiles = {}
self._lists = {}
self.trackedMissiles = Bastion.List:New()
self.allMissiles = Bastion.List:New()
self.trackingParams = {}
return self
end
-- Register a custom list with a callback
---@param name string
---@param cb fun(missile: Tinkr.Missile): boolean | any
---@return Bastion.List | false
function MissileManager:RegisterList(name, cb)
if self._lists[name] then
return false
end
self._lists[name] = {
list = Bastion.List:New(),
cb = cb,
}
return self._lists[name].list
end
-- reset custom lists
---@return nil
function MissileManager:ResetLists()
for _, list in pairs(self._lists) do
list.list:clear()
end
end
function MissileManager:Reset()
self.missiles = {}
self.trackedMissiles:clear()
self.allMissiles:clear()
self:ResetLists()
end
-- Refresh custom lists
---@param missile Tinkr.Missile
---@return nil
function MissileManager:EnumLists(missile)
for _, list in pairs(self._lists) do
local r = list.cb(missile)
if r then
list.list:push(r)
end
end
end
---@param params Bastion.MissileManager.TrackingParams
function MissileManager:TrackMissile(params)
table.insert(self.trackingParams, params)
end
---@param missileObj Bastion.Missile
function MissileManager:EnumTrackingParams(missileObj)
local tracked = false
for i, trackingParam in ipairs(self.trackingParams) do
if (not trackingParam.source or missileObj:GetSourceUnit():IsUnit(trackingParam.source)) and
(not trackingParam.target or missileObj:GetTargetUnit():IsUnit(trackingParam.target)) and
(not trackingParam.spellId or missileObj._missile.spellId == trackingParam.spellId) and
(not trackingParam.spellVisualId or missileObj._missile.spellVisualId == trackingParam.spellVisualId)
then
tracked = true
trackingParam.callback(missileObj)
end
end
return tracked
end
-- Get a list
---@param name string
---@return Bastion.List
function MissileManager:GetList(name)
return self._lists[name].list
end
function MissileManager:Refresh()
self:Reset()
local missiles = Missiles()
if type(missiles) == "table" then
for _, missile in ipairs(missiles) do
table.insert(self.missiles, missile)
self:EnumLists(missile)
local missileObj = Bastion.Missile:New(missile)
self.allMissiles:push(missileObj)
if self:EnumTrackingParams(missileObj) then
self.trackedMissiles:push(missileObj)
end
end
end
end
---@param params { source?: Bastion.Unit, target?: Bastion.Unit, spellId?: number, spellVisualId?: number }
function MissileManager:GetMissiles(params)
---@type Bastion.Missile[]
local missiles = {}
for _, missile in ipairs(self.trackedMissiles) do
if (not params.source or missile:GetSourceUnit():IsUnit(params.source)) and
(not params.target or missile:GetTargetUnit():IsUnit(params.target)) and
(not params.spellId or missile._missile.spellId == params.spellId) and
(not params.spellVisualId or missile._missile.spellVisualId == params.spellVisualId)
then
table.insert(missiles, missile)
end
end
return missiles
end
Bastion.MissileManager = MissileManager:New()

@ -22,8 +22,10 @@ function ObjectManager:New()
self.explosives = Bastion.List:New()
self.friends = Bastion.List:New()
self.incorporeal = Bastion.List:New()
self.missiles = Bastion.List:New()
self.others = Bastion.List:New()
return self
end
@ -62,6 +64,7 @@ function ObjectManager:Reset()
self.explosives:clear()
self.friends:clear()
self.incorporeal:clear()
self.missiles:clear()
self.others:clear()
self:ResetLists()
end
@ -100,8 +103,7 @@ function ObjectManager:Refresh()
if objectGUID then
local unit = Bastion.UnitManager:GetObject(objectGUID)
if not unit then
unit = Bastion.Unit:New(object)
Bastion.UnitManager:SetObject(unit)
unit = Bastion.UnitManager:SetObject(Bastion.Unit:New(object))
end
if objectType == 5 and ObjectCreatureType(object) == 8 then
@ -116,7 +118,7 @@ function ObjectManager:Refresh()
self.friends:push(unit)
elseif unit:IsEnemy() then
self.enemies:push(unit)
if unit:InCombatOdds() > 80 or unit:IsAffectingCombat() then
if unit:IsAffectingCombat() or unit:InCombatOdds() > 80 then
self.activeEnemies:push(unit)
end
else
@ -125,6 +127,13 @@ function ObjectManager:Refresh()
end
end
end
local missiles = Missiles()
if type(missiles) == "table" then
for _, missile in pairs(missiles) do
self.missiles:push(missile)
end
end
end
Bastion.ObjectManager = ObjectManager:New()

@ -63,6 +63,7 @@ Bastion = ...
---@field target Bastion.Unit | false
---@field traits Bastion.Spell.Traits
---@field wasLooking boolean
---@field tickDuration false | number | fun(self: Bastion.Spell): number
local Spell = {
CastableIfFunc = false,
damage = 0,
@ -132,6 +133,7 @@ function Spell:New(id)
}
self.target = false
self.wasLooking = false
self.tickDuration = false
return self
end
@ -231,7 +233,7 @@ function Spell:AddOverrideSpell(spell, func)
end
-- Cast the spell
---@param unit Bastion.Unit
---@param unit? false | Bastion.Unit
---@param condition? string | fun(self:Bastion.Spell):boolean
---@return boolean
function Spell:Cast(unit, condition)
@ -258,7 +260,7 @@ function Spell:Cast(unit, condition)
-- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast
local u = unit and unit:GetOMToken() or self.traits.target.player and "none" or "none"
local u = unit ~= false and unit:GetOMToken() or self.traits.target.player and "none" or "none"
if type(u) == "string" and string.find(u, "nameplate") then
---@diagnostic disable-next-line: cast-local-type
u = Object(u)
@ -329,11 +331,16 @@ function Spell:IsKnown(includeOverrides)
return isKnown
end
function Spell:GetSpellLossOfControlCooldown()
local start, duration = GetSpellLossOfControlCooldown(self:IsOverridden() and self:GetName() or self:GetID()) or 0, 0
return start, duration
end
-- Check if the spell is on cooldown
---@return boolean
function Spell:IsOnCooldown()
local spellIDName = self:IsOverridden() and self:GetName() or self:GetID()
return select(2, GetSpellCooldown(spellIDName)) > 0
return select(2, GetSpellCooldown(spellIDName)) > 0 or select(2, self:GetSpellLossOfControlCooldown()) > 0
end
---@param byId? boolean
@ -417,14 +424,8 @@ end
-- Check if the spell is castable
function Spell:Castable()
if not self:EvaluateTraits() then
return false
end
if self:GetCastableFunction() and not self:GetCastableFunction()(self) then
return false
end
return self:IsKnownAndUsable(type(self.traits.cast.override) ~= nil and self.traits.cast.override or nil)
return self:EvaluateTraits() and (not self:GetCastableFunction() or self:GetCastableFunction()(self)) and
self:IsKnownAndUsable(type(self.traits.cast.override) ~= nil and self.traits.cast.override or nil)
end
-- Set a script to check if the spell is castable
@ -800,4 +801,32 @@ function Spell:UpdateAura(spell, source, target)
self.auras[spell:GetID()].lastApplied = GetTime()
end
function Spell:GetTickDuration()
if type(self.tickDuration) == "function" then
return self.tickDuration(self)
else
return self.tickDuration
end
end
---@param duration (number) | fun(self: Bastion.Spell): (number)
function Spell:TickDuration(duration)
self.tickDuration = duration
end
---@param params { source: Bastion.Unit, target: Bastion.Unit, spellVisualId?: number }
function Spell:GetMissiles(params)
return Bastion.MissileManager:GetMissiles({
source = params.source,
target = params.target,
spellId = self:GetID(),
spellVisualId = params.spellVisualId,
})
end
---@param params { source: Bastion.Unit, target: Bastion.Unit, spellVisualId?: number}
function Spell:InFlight(params)
return #self:GetMissiles(params) > 0
end
Bastion.Spell = Spell

@ -15,7 +15,7 @@ local Cache = Bastion.Globals.UnitInfo
local TimeToDie = {
Settings = {
-- Refresh time (seconds) : min=0.1, max=2, default = 0.1
Refresh = 0.5,
Refresh = 0.1,
-- History time (seconds) : min=5, max=120, default = 10+0.4
HistoryTime = 10 + 0.4,
-- Max history count : min=20, max=500, default = 100
@ -84,11 +84,21 @@ function TimeToDie:Refresh()
-- Check if we have seen one time this unit, if we don't then initialize it.
-- Also check to see if the unit's health percentage is higher than the last one we saw. If so, we recreate the table.
if not unitTable or healthPercentage > unitTable.history[1].percentage then
unitTable = {
history = {},
time = currentTime
}
units[unitGUID] = unitTable
if unitTable and healthPercentage > unitTable.history[1].percentage then
for i = 1, #unitTable.history do
if healthPercentage > unitTable.history[i].percentage then
unitTable.history[i].percentage = healthPercentage
else
break
end
end
else
unitTable = {
history = {},
time = currentTime
}
units[unitGUID] = unitTable
end
end
local history = unitTable.history
local time = currentTime - unitTable.time

@ -92,7 +92,7 @@ end
-- Check if the unit is valid
---@return boolean
function Unit:IsValid()
return self:GetOMToken() ~= nil and self:Exists()
return (self:GetOMToken() ~= "none" and self:GetOMToken() ~= nil) and self:Exists()
end
-- Check if the unit exists
@ -291,7 +291,7 @@ function Unit:GetOMToken()
if not self.unit then
return "none"
end
return self.unit:unit()
return self.unit:unit() or "none"
end
-- Is the unit a target
@ -1339,8 +1339,8 @@ end
---@return Bastion.Unit
function Unit:Target()
return self:HasTarget() and Bastion.UnitManager:Get(ObjectTarget(self:GetOMToken()):unit()) or
Bastion.UnitManager:Get("none")
local objTarget = ObjectTarget(self:GetOMToken())
return objTarget and Bastion.UnitManager:Get(objTarget:unit() or "none") or Bastion.UnitManager:Get("none")
end
local dummyUnits = {
@ -1581,7 +1581,7 @@ function Unit:TimeToDie2(minSamples)
v = self:TimeToX(self:SpecialTTDPercentage(id), minSamples)
if v >= 0 then
ttd[minSamples] = v
Bastion.Globals.UnitInfo:Set(unitGuid, unitInfo, .5)
Bastion.Globals.UnitInfo:Set(unitGuid, unitInfo, .1)
end
end
@ -1646,4 +1646,144 @@ function Unit:BossTimeToDieIsNotValid(minSamples)
return true
end
---@param spell Bastion.Spell
---@param source? Bastion.Unit
function Unit:GetAuraTickRate(spell, source)
local unit = self.unit.unit()
local aura = source and self:GetAuras():FindFrom(spell, source) or self:GetAuras():Find(spell)
if aura:IsValid() and unit then
local tooltipInfo = C_TooltipInfo.GetUnitDebuffByAuraInstanceID(unit, aura:GetAuraInstanceID())
if tooltipInfo and tooltipInfo.lines then
for _, line in ipairs(tooltipInfo.lines) do
if line.leftText then
local rate = line.leftText:gmatch("every (%d*%.?%d+) sec")
for tickRate in rate do
return tonumber(tickRate) or 0
end
end
end
end
end
return false
end
function Unit:Effects()
return UnitEffects(self:GetOMToken())
end
function Unit:IsSitting()
return UnitIsSitting(self:GetOMToken())
end
function Unit:GetAnimKit()
return GetUnitAnimKit(self:GetOMToken())
end
function Unit:IsEating()
local effects = self:Effects()
if not effects or #effects == 0 then
return false
end
for _, effect in ipairs(effects) do
if effect.spellId and effect.spellId == 396921 then -- https://www.wowhead.com/spell=396921
return true
end
end
return false
end
function Unit:IsDrinking()
local effects = self:Effects()
if not effects or #effects == 0 then
return false
end
for _, effect in ipairs(effects) do
if effect.spellId and effect.spellId == 396921 then -- https://www.wowhead.com/spell=396921
return true
end
end
return false
end
function Unit:IsEatingOrDrinking()
return self:IsEating() or self:IsDrinking()
end
function Unit:IsSummoning()
local effects = self:Effects()
if not effects or #effects == 0 then
return false
end
for _, effect in ipairs(effects) do
if effect.spellId and effect.spellId == 59782 then
return true
end
end
return false
end
function Unit:GetLosssOfControlCount()
return C_LossOfControl.GetActiveLossOfControlDataCountByUnit(self:GetOMToken())
end
---@param index number
function Unit:LosssOfControlData(index)
return C_LossOfControl.GetActiveLossOfControlDataByUnit(self:GetOMToken(), index)
end
function Unit:Available()
return self:Exists()
and self:IsAlive()
and self:GetLosssOfControlCount() == 0
and not UnitOnTaxi(self:GetOMToken())
and not UnitInVehicle(self:GetOMToken())
and not self:IsCastingOrChanneling()
and (self:IsAffectingCombat() or (not self:IsSitting() and not self:IsSummoning()))
end
---@param params { target?: Bastion.Unit, spellId?: number, spellVisualId?: number }
function Unit:GetOutgoingMissles(params)
local missiles = Missiles()
---@type Tinkr.Missile[]
local results = {}
if type(missiles) == "table" and self:IsValid() then
for i, missile in ipairs(missiles) do
local missileSource = missile.source:unit()
if missileSource then
if UnitIsUnit(self:GetOMToken(), missileSource)
and (not params.target or UnitIsUnit(missile.target:unit() or "none", params.target:GetOMToken()))
and (not params.spellId or params.spellId == missile.spellId)
and (not params.spellVisualId or params.spellVisualId == missile.spellVisualId)
then
table.insert(results, missile)
end
end
end
end
return results
end
---@param params { source?: Bastion.Unit, spellId?: number, spellVisualId?: number }
function Unit:GetIncomingMissiles(params)
local missiles = Missiles()
---@type Tinkr.Missile[]
local results = {}
if type(missiles) == "table" and self:IsValid() then
for i, missile in ipairs(missiles) do
local missileTarget = missile.target:unit()
if missileTarget then
if UnitIsUnit(self:GetOMToken(), missileTarget)
and (not params.source or UnitIsUnit(params.source:unit() or "none", self:GetOMToken()))
and (not params.spellId or params.spellId == missile.spellId)
and (not params.spellVisualId or params.spellVisualId == missile.spellVisualId)
then
table.insert(results, missile)
end
end
end
end
return results
end
Bastion.Unit = Unit

@ -16,10 +16,6 @@ local Unit = Bastion.Unit
---@field objects table<string | WowGameObject, Bastion.Unit>
---@field cache Bastion.Cache
local UnitManager = {
units = {},
customUnits = {},
objects = {},
cache = {},
}
---@param k UnitId
@ -49,19 +45,11 @@ function UnitManager:__index(k)
return self.objects[kguid]
end
-- if not Validate(k) then
-- error("UnitManager:Get - Invalid token: " .. k)
-- end
if self.objects[kguid] == nil then
local o = Object(k)
if o then
local unit = Unit:New(o)
self:SetObject(unit)
if self.objects[kguid] then
return self.objects[kguid]
end
end
end
@ -75,12 +63,13 @@ function UnitManager:New()
local self = setmetatable({}, UnitManager)
self.units = {}
self.customUnits = {}
self.objects = {}
self.cache = Bastion.Cache:New()
return self
end
-- Get or create a unit
---@param token UnitId
---@param token UnitId | TinkrObjectReference
function UnitManager:Get(token)
-- if not Validate(token) then
-- error("UnitManager:Get - Invalid token: " .. token)
@ -119,10 +108,31 @@ function UnitManager:GetObject(guid)
return self.objects[guid]
end
-- Get a unit by guid or create a new one if it does not exist.
---@param unit string | WowGameObject
---@return Bastion.Unit?
function UnitManager:GetOrCreateObject(unit)
local guid = type(unit) == "string" and unit or ObjectGUID(unit)
if guid then
local unitObj = self:GetObject(guid)
if unitObj and not unitObj:IsValid() then
local object = type(unit) ~= "string" and unit or Object(guid)
if object then
unitObj = Bastion.UnitManager:SetObject(Bastion.Unit:New(object))
end
end
return unitObj
end
end
-- Set a unit by guid
---@param unit Bastion.Unit
function UnitManager:SetObject(unit)
self.objects[unit:GetGUID()] = unit
local guid = unit:GetGUID()
if guid then
self.objects[guid] = unit
return self.objects[guid]
end
end
-- Create a custom unit and cache it for .5 seconds
@ -146,6 +156,12 @@ function UnitManager:CreateCustomUnit(token, cb)
return cachedUnit
end
function UnitManager:ResetObjects()
for k, object in pairs(self.objects) do
self.objects[k] = nil
end
end
---@description Enumerates all friendly units in the battlefield
---@param cb fun(unit: Bastion.Unit):boolean
---@return nil

Loading…
Cancel
Save