Bastion aims to serve as a highly performant, simplisitic, and expandable World of Warcraft data visualization framework.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Bastion/src/Spell/Spell.lua

986 lines
26 KiB

---@type Tinkr
local Tinkr,
---@class Bastion
Bastion = ...
---@class Bastion.Spell.Traits.Cast
---@field moving boolean
---@field dead boolean
---@field global boolean
---@field casting boolean
---@field channeling boolean
---@field override boolean
---@field talent boolean | spellId
---@field power boolean
---@field precast false | fun(self:Bastion.Spell):boolean
---@class Bastion.Spell.Traits.Cost
---@field type Enum.PowerType
---@field cost number
---@field minCost number
---@field requiredAuraID number
---@field costPercent number
---@field costPerSecond number
---@class Bastion.Spell.Traits.Target
---@field exists boolean
---@field player boolean
---@field facing boolean
---@class Bastion.Spell.Traits.Aura
---@field track boolean
---@class Bastion.Spell.Traits
---@field cast Bastion.Spell.Traits.Cast
---@field target Bastion.Spell.Traits.Target
---@field cost Bastion.Spell.Traits.Cost[]
---@field aura Bastion.Spell.Traits.Aura
---@class Bastion.Spell.Traits.Cast.Params : Bastion.Spell.Traits.Cast, { [string]?: boolean }
---@class Bastion.Spell.Traits.Target.Params : Bastion.Spell.Traits.Target, { [string]?: boolean }
---@class Bastion.Spell.Traits.Cost.Params : Bastion.Spell.Traits.Cost[]
---@class Bastion.Spell.Traits.Aura.Params : Bastion.Spell.Traits.Aura
---@class Bastion.Spell.Traits.Params
---@field cast? Bastion.Spell.Traits.Cast.Params
---@field target? Bastion.Spell.Traits.Target.Params
---@field cost? Bastion.Spell.Traits.Cost.Params
---@field aura? Bastion.Spell.Traits.Aura.Params
---@class Bastion.Spell.Aura
---@field spell Bastion.Spell
---@field source? Bastion.Unit
---@field target? Bastion.Unit
---@field lastApplied number
-- Create a new Spell class
---@class Bastion.Spell
---@field auras table<spellId, Bastion.Spell.Aura>
---@field CastableIfFunc false | fun(self:Bastion.Spell):boolean
---@field conditions { [string]: { func: fun(self:Bastion.Spell):boolean } }
---@field damage number
---@field damageFormula false | fun(self:Bastion.Spell):number
---@field lastCastAt number | false
---@field lastCastAttempt number | false
---@field OnCastFunc fun(self:Bastion.Spell) | false
---@field overrides { [spellId]: fun(self: Bastion.Spell): Bastion.Spell }
---@field PostCastFunc fun(self:Bastion.Spell) | false
---@field PreCastFunc fun(self:Bastion.Spell) | false
---@field release_at false
---@field spellID number
---@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,
damageFormula = false,
lastCastAt = false,
lastCastAttempt = false,
OnCastFunc = false,
PostCastFunc = false,
PreCastFunc = false,
release_at = false,
}
local usableExcludes = {
[18562] = true,
}
function Spell:__index(k)
local response = Bastion.ClassMagic:Resolve(Spell, k)
if response == nil then
response = rawget(self, k)
end
if response == nil then
error("Spell:__index: " .. k .. " does not exist")
end
return response
end
-- Equals
---@param other Bastion.Spell
function Spell:__eq(other)
return self:GetID() == other:GetID()
end
-- tostring
function Spell:__tostring()
return "Bastion.__Spell(" .. self:GetID() .. ")" .. " - " .. self:GetName()
end
-- Constructor
---@param id number
function Spell:New(id)
---@class Bastion.Spell
local self = setmetatable({}, Spell)
local spellInfo = C_Spell.GetSpellInfo(id)
local maxRange = spellInfo and spellInfo.maxRange or 0
local minRange = spellInfo and spellInfo.minRange or 0
self.auras = {}
self.overrides = {}
self.conditions = {}
self.spellID = id
self.lastCastGUID = false
self.castGUID = false
self.lastCastSuccess = false
---@type 0 | 1 | 2 0 = SPELL_RANGE_DEFAULT, 1 = SPELL_RANGE_MELEE, 2 = SPELL_RANGE_RANGED
self.rangeEntry = 0
self.minRange = minRange
self.maxRange = maxRange
self.isMelee = minRange == 0 and maxRange == 0
self.traits = {
cast = {
moving = true,
dead = false,
global = false,
casting = false,
channeling = false,
override = false,
talent = false,
power = false,
precast = false,
},
target = {
exists = true,
player = false,
facing = false,
},
cost = {},
aura = {
track = false,
},
}
self.target = false
self.wasLooking = false
self.tickDuration = false
return self
end
---@return string
function Spell:GetDescription()
return C_Spell.GetSpellDescription(self:GetID()) or ""
end
-- Duplicator
function Spell:Fresh()
return Spell:New(self:GetID())
end
-- Get the spells id
---@param ignoreOverride? boolean
---@return number
function Spell:GetID(ignoreOverride)
return ignoreOverride and self.spellID or self:IsOverridden() and self:OverrideSpellID() or self.spellID
end
-- Add post cast func
---@param func fun(self:Bastion.Spell)
function Spell:PostCast(func)
self.PostCastFunc = func
return self
end
function Spell:GetSpellInfo()
return C_Spell.GetSpellInfo(self:GetID())
end
-- Get the spells name
---@return string
function Spell:GetName()
local spellName = Bastion.Globals.SpellName[self:GetID()]
if not spellName then
local spellInfo = self:GetSpellInfo()
spellName = spellInfo and spellInfo.name or ""
end
return spellName
end
-- Get the spells icon
---@return number
function Spell:GetIcon()
local spellInfo = self:GetSpellInfo()
return spellInfo and spellInfo.iconID or 0
end
---@param byId? boolean
function Spell:GetCooldownInfo(byId)
return C_Spell.GetSpellCooldown(byId and self:GetID() or self:GetName())
end
-- Get the spells cooldown
---@param byId? boolean
---@return number
function Spell:GetCooldown(byId)
local cdInfo = self:GetCooldownInfo(byId)
if cdInfo then
return cdInfo.duration
end
return 0
end
-- Return the castable function
function Spell:GetCastableFunction()
return self.CastableIfFunc
end
-- Return the precast function
function Spell:GetPreCastFunction()
return self.PreCastFunc
end
-- Get the on cast func
function Spell:GetOnCastFunction()
return self.OnCastFunc
end
-- Get the spells cooldown remaining
---@param byId? boolean
---@return number
function Spell:GetCooldownRemaining(byId)
local cdInfo = self:GetCooldownInfo(byId)
if cdInfo then
if cdInfo.startTime == 0 then return 0 end
return cdInfo.startTime + cdInfo.duration - GetTime()
end
return 0
end
-- Get the spell count
---@param byId? boolean
---@return number
function Spell:GetCount(byId)
return C_Spell.GetSpellCastCount((byId ~= nil and byId) and self:GetID() or self:GetName())
end
-- On cooldown
---@return boolean
function Spell:OnCooldown()
return self:GetCooldownRemaining() > 0
end
-- Clear castable function
---@return Bastion.Spell
function Spell:ClearCastableFunction()
self.CastableIfFunc = false
return self
end
---@param spell Bastion.Spell
---@param func fun(self: Bastion.Spell): boolean
function Spell:AddOverrideSpell(spell, func)
self.overrides[spell:GetID()] = func
end
-- Cast the spell
---@param unit? Bastion.Unit
---@param condition? string | fun(self:Bastion.Spell, target?: Bastion.Unit):boolean
---@return boolean
function Spell:Cast(unit, condition)
if not self:Castable(unit) then
return false
end
if condition then
if type(condition) == "string" and not self:EvaluateCondition(condition) then
return false
elseif type(condition) == "function" and not condition(self, unit) then
return false
end
end
-- Call pre cast function
if self:GetPreCastFunction() then
self:GetPreCastFunction()(self)
end
-- Check if the mouse was looking
self.wasLooking = IsMouselooking() --[[@as boolean]]
-- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast
local target = unit and unit:GetOMToken() or self.traits.target.player and "player" or "none"
if type(target) == "string" and string.find(target, "nameplate") then
target = Object(target):unit()
end
-- Cast the spell
CastSpellByName(self:GetName(), target)
--[[ local tgt = Object(target)
local tgtname = target
if tgt then
tgtname = tgt:name() or target
target = UnitTokenFromGUID(tgt:guid() or "")
end
Tinkr.Util.Tools.Console.print("|cff1ebf3c[Cast]|r|cffFFCC00[" ..
self:GetName() .. "]|r->" .. tgtname .. "[" .. target .. "]") ]]
SpellCancelQueuedSpell()
Bastion.Util:Debug("Casting", self)
-- Set the last cast time
self.lastCastAttempt = GetTime()
-- Call post cast function
if self:GetOnCastFunction() then
self:GetOnCastFunction()(self)
end
return true
end
-- ForceCast the spell
---@param unit Bastion.Unit
---@return boolean
function Spell:ForceCast(unit)
-- Check if the mouse was looking
self.wasLooking = IsMouselooking() --[[@as boolean]]
-- if unit:GetOMToken() contains 'nameplate' then we need to use Object wrapper to cast
local target = unit and unit:GetOMToken() or self.traits.target.player and "player" or "none"
if type(target) == "string" and string.find(target, "nameplate") then
target = Object(target):unit()
end
-- Cast the spell
CastSpellByName(self:GetName(), target)
SpellCancelQueuedSpell()
Bastion.Util:Debug("Force Casting", self)
-- Set the last cast time
self.lastCastAttempt = GetTime()
return true
end
-- Get post cast func
function Spell:GetPostCastFunction()
return self.PostCastFunc
end
function Spell:OverrideSpellID()
return C_Spell.GetOverrideSpell(self.spellID)
end
function Spell:IsOverridden()
return self:OverrideSpellID() ~= self.spellID
end
-- Check if the spell is known
---@param includeOverrides? boolean
---@return boolean
function Spell:IsKnown(includeOverrides)
local isKnown = (includeOverrides or self.traits.cast.override) and IsSpellKnownOrOverridesKnown(self:GetID()) or
IsSpellKnown(self:GetID()) or
IsPlayerSpell(self:GetID())
return isKnown
end
function Spell:GetSpellLossOfControlCooldown()
local start, duration =
C_Spell.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 spellCooldownInfo = self:GetCooldownInfo(self:IsOverridden() and false)
if spellCooldownInfo and spellCooldownInfo.duration then
return spellCooldownInfo.duration > 0 or select(2, self:GetSpellLossOfControlCooldown()) > 0
end
return false
end
---@param byId? boolean
---@return boolean, boolean
function Spell:IsUsableSpell(byId)
return C_Spell.IsSpellUsable(byId and self:GetID() or self:GetName())
end
-- Check if the spell is usable
---@param byId? boolean
---@return boolean
function Spell:IsUsable(byId)
local usable, noMana = self:IsUsableSpell(byId)
return usable and not noMana
end
-- Check if the spell is castable
---@param override? boolean
---@return boolean
function Spell:IsKnownAndUsable(override)
return self:IsKnown(override or self.traits.cast.override) and not self:IsOnCooldown() and
self:IsUsable(override ~= nil and true or false)
end
---@param traits Bastion.Spell.Traits.Params
function Spell:SetTraits(traits)
for _, trait in pairs({ "cast", "target", "aura" }) do
if type(traits[trait]) == "table" then
for k, _ in pairs(self.traits[trait]) do
if traits[trait][k] ~= nil then
self.traits[trait][k] = traits[trait][k]
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
end
---@param target? Bastion.Unit
function Spell:EvaluateTraits(target)
local player = Bastion.Globals.UnitManager:Get("player")
if self.traits.cast.precast and not self.traits.cast.precast(self) then
return false
end
if self.traits.target.facing and target and not player:IsFacing(target) then
return false
end
if self.traits.cast.power and not self:HasPower() then
return false
end
if not self.traits.cast.global and player:GetGCD() > 0 then
return false
end
if not self.traits.cast.moving and player:IsMoving() then
return false
end
if not self.traits.cast.dead and player:IsDead() then
return false
end
if not self.traits.cast.casting and player:IsCasting() then
return false
end
if not self.traits.cast.channeling and player:IsChanneling() then
return false
end
if self.traits.target.exists then
if (self.traits.target.player and not player:Exists()) then
return false
elseif (target and (target == Bastion.Globals.UnitManager:Get("none") or not target:Exists())) or (not target and not self:TargetExists()) then
return false
end
end
if self.traits.cast.talent and not IsPlayerSpell(tonumber(self.traits.cast.talent) or self:GetID()) then
return false
end
return true
end
-- Check if the spell is castable
---@param target? Bastion.Unit
function Spell:Castable(target)
return self:IsKnownAndUsable(self.traits.cast.override) and self:EvaluateTraits(target) and
(not self:GetCastableFunction() or self:GetCastableFunction()(self))
end
-- Set a script to check if the spell is castable
---@param func fun(spell:Bastion.Spell):boolean
function Spell:CastableIf(func)
self.CastableIfFunc = func
return self
end
-- Set a script to run before the spell has been cast
---@param func fun(spell:Bastion.Spell)
function Spell:PreCast(func)
self.PreCastFunc = func
return self
end
-- Set a script to run after the spell has been cast
---@param func fun(spell:Bastion.Spell)
function Spell:OnCast(func)
self.OnCastFunc = func
return self
end
-- Get was looking
---@return boolean
function Spell:GetWasLooking()
return self.wasLooking
end
-- Click the spell
---@param x number|Bastion.Vector3
---@param y? number
---@param z? number
---@return boolean
function Spell:Click(x, y, z)
if type(x) == "table" then
z, y, x = x.z, x.y, x.x
end
---@cast x number
---@cast y number
---@cast z number
if IsSpellPending() == 64 then
MouselookStop()
Click(x, y, z)
if self:GetWasLooking() then
MouselookStart()
end
return true
end
return false
end
-- Check if the spell is castable and cast it
---@param unit Bastion.Unit
function Spell:Call(unit)
return self:Cast(unit)
end
-- Check if the spell is castable and cast it
---@return boolean?
function Spell:HasRange()
return C_Spell.SpellHasRange(self:GetName())
end
-- Get the range of the spell
---@return number
---@return number
function Spell:GetRange()
--local _, _, _, _, minRange, maxRange, _, _ = GetSpellInfo(self:GetID())
return self.maxRange, self.minRange
end
---@param source Bastion.Unit
---@param target Bastion.Unit
function Spell:CheckRange(source, target)
return self.isMelee and source:InMelee(target) or source:GetCombatDistance(target) <= self.maxRange
end
---@param target? Bastion.Unit
function Spell:GetMinMaxRange(target)
local rangeMod, minRange, maxRange = 0.0, 0.0, 0.0
local unitCaster = Bastion.Globals.UnitManager:Get("player")
if self:HasRange() then
minRange, maxRange = self:GetRange()
-- probably melee
if self.isMelee then
rangeMod = unitCaster:GetMeleeRange(target or unitCaster)
else
local meleeRange = 0
--[[ if maxRange > 0 then
meleeRange = unitCaster:GetMeleeRange(target or unitCaster)
end ]]
minRange = (minRange == maxRange and minRange or not target and 0 or minRange)
end
end
maxRange = maxRange + rangeMod
return minRange, maxRange
end
-- Check if the spell is in range of the unit
---@param unit Bastion.Unit
---@return boolean
function Spell:IsInRange(unit)
if unit:IsUnit(Bastion.Globals.UnitManager:Get("player")) then
return true
end
local hasRange = self:HasRange()
if hasRange == false then
return true
end
local inRange = C_Spell.IsSpellInRange(self:GetName(), unit:GetOMToken()) or false
if inRange then
return true
end
return Bastion.Globals.UnitManager:Get("player"):InMelee(unit)
end
-- Get the last cast time
---@return number | false
function Spell:GetLastCastTime()
return self.lastCastAt
end
-- Get time since last cast
---@return number
function Spell:GetTimeSinceLastCast()
if not self:GetLastCastTime() then
return math.huge
end
return GetTime() - self:GetLastCastTime()
end
-- Get the time since the last cast attempt
---@return number
function Spell:GetTimeSinceLastCastAttempt()
if not self.lastCastAttempt then
return math.huge
end
return GetTime() - self.lastCastAttempt
end
function Spell:GetCastGuid()
return self.castGUID
end
---@param time number
---@param success boolean
---@param castGUID string
function Spell:SetLastCast(time, success, castGUID)
self.lastCastSuccess = success
if success and self.lastCastGUID and self.lastCastGUID == castGUID then
self.lastCastAt = self.lastCastAttempt
self.castGUID = self.lastCastGUID
end
end
---@param time number
---@param castGUID string
function Spell:SetLastCastAttempt(time, castGUID)
self.lastCastSuccess = false
self.lastCastAttempt = time
self.lastCastGUID = castGUID
end
function Spell:GetChargeInfo()
return C_Spell.GetSpellCharges(self:GetID())
end
-- Get the spells charges
function Spell:GetCharges()
local chargeInfo = self:GetChargeInfo()
return chargeInfo and chargeInfo.currentCharges or 0
end
function Spell:GetMaxCharges()
local chargeInfo = self:GetChargeInfo()
return chargeInfo and chargeInfo.maxCharges or 0
end
---@return number
function Spell:GetCastLength()
local spellInfo = self:GetSpellInfo()
return spellInfo and spellInfo.castTime or 0
end
-- Get the full cooldown (time until all charges are available)
---@return number
function Spell:GetFullRechargeTime()
local spellChargeInfo = self:GetChargeInfo()
if not spellChargeInfo or spellChargeInfo.currentCharges == spellChargeInfo.maxCharges then
return 0
end
return (spellChargeInfo.maxCharges - self:GetChargesFractional()) * spellChargeInfo.cooldownDuration
end
function Spell:Recharge()
local spellChargeInfo = self:GetChargeInfo()
if not spellChargeInfo or spellChargeInfo.currentCharges == spellChargeInfo.maxCharges then
return 0
end
local recharge = spellChargeInfo.cooldownStartTime + spellChargeInfo.cooldownDuration - GetTime()
return recharge > 0 and recharge or 0
end
-- Get the spells charges plus fractional
---@return number
function Spell:GetChargesFractional()
local spellChargeInfo = self:GetChargeInfo()
if not spellChargeInfo or spellChargeInfo.currentCharges == spellChargeInfo.maxCharges then
return spellChargeInfo.currentCharges
end
return spellChargeInfo.currentCharges +
((spellChargeInfo.cooldownDuration - self:Recharge()) / spellChargeInfo.cooldownDuration)
end
-- Get the spells charges remaining
---@return number
function Spell:GetChargesRemaining()
local spellChargeInfo = self:GetChargeInfo()
return spellChargeInfo and spellChargeInfo.currentCharges or 0
end
-- Create a condition for the spell
---@param name string
---@param func fun(self:Bastion.Spell):boolean
---@return Bastion.Spell
function Spell:Condition(name, func)
self.conditions[name] = {
func = func,
}
return self
end
-- Get a condition for the spell
---@param name string
---@return { func: fun(self: Bastion.Spell): boolean } | nil
function Spell:GetCondition(name)
local condition = self.conditions[name]
if condition then
return condition
end
return nil
end
-- Evaluate a condition for the spell
---@param name string
---@return boolean
function Spell:EvaluateCondition(name)
local condition = self:GetCondition(name)
if condition then
return condition.func(self)
end
return false
end
-- Check if the spell has a condition
---@param name string
---@return boolean
function Spell:HasCondition(name)
local condition = self:GetCondition(name)
if condition then
return true
end
return false
end
-- Set the spells target
---@param unit Bastion.Unit
---@return Bastion.Spell
function Spell:SetTarget(unit)
self.target = unit
return self
end
-- Get the spells target
function Spell:GetTarget()
return self.target
end
function Spell:TargetExists()
return self:GetTarget() and self:GetTarget():Exists()
end
-- IsEnrageDispel
---@return boolean
function Spell:IsEnrageDispel()
return ({
[2908] = true, -- Soothe
[19801] = true, -- Tranq Shot
})[self:GetID()]
end
-- IsMagicDispel
---@return boolean
function Spell:IsMagicDispel()
return ({
[77130] = true, -- Purify Spirit
[115450] = true, -- Detox
[4987] = true, -- Cleanse
[527] = true, -- Purify
[32375] = true, -- Mass Dispel
[89808] = true, -- Singe Magic
})[self:GetID()]
end
-- IsCurseDispel
---@return boolean
function Spell:IsCurseDispel()
return ({
[77130] = true, -- Purify Spirit
[2782] = true,
[475] = true, -- Remove Curse
[51886] = true, -- Cleanse Spirit
})[self:GetID()]
end
-- IsPoisonDispel
---@return boolean
function Spell:IsPoisonDispel()
return ({
[2782] = true,
[115450] = true, -- Detox
[4987] = true, -- Cleanse
[213644] = true, -- Cleanse Toxins
})[self:GetID()]
end
-- IsDiseaseDispel
---@return boolean
function Spell:IsDiseaseDispel()
return ({
[115450] = true, -- Detox
[4987] = true, -- Cleanse
[213644] = true, -- Cleanse Toxins
[527] = true, -- Purify
[213634] = true, -- Purify Disease
})[self:GetID()]
end
-- IsSpell
---@param spell Bastion.Spell
---@return boolean
function Spell:IsSpell(spell)
return self:GetID() == spell:GetID()
end
---@return SpellPowerCostInfo[]?
function Spell:GetCostInfo()
return C_Spell.GetSpellPowerCost(self:GetID())
end
function Spell:HasPower()
local costs = self:GetCostInfo()
local checked = {}
local hasPower = costs and #costs > 0 and false or true
if costs and 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.Globals.UnitManager:Get("player"):GetPower(cost.type) >= powerCost
checked[cost.type] = true
if not hasPower then
return false
end
end
end
end
end
return hasPower
end
-- GetCost
---@return number
function Spell:GetCost()
local cost = self:GetCostInfo()
return cost and cost[1] and cost[1].cost or 0
end
-- IsFree
---@return boolean
function Spell:IsFree()
return self:GetCost() == 0
end
---@param damageFormula fun(self:Bastion.Spell): number
function Spell:RegisterDamageFormula(damageFormula)
self.damageFormula = damageFormula
end
---@return number
function Spell:Damage()
if self.damageFormula then
return self:damageFormula()
else
return self.damage
end
end
---@param target? Bastion.Unit
---@param source? "any" | Bastion.Unit
function Spell:GetAura(target, source)
if type(target) == "nil" then
target = Bastion.Globals.UnitManager:Get("player")
end
if type(source) == "nil" then
source = Bastion.Globals.UnitManager:Get("player")
end
return target:GetAuras():FindFrom(self, source)
end
---@param spell Bastion.Spell
---@param source? Bastion.Unit
---@param target? Bastion.Unit
function Spell:TrackAura(spell, source, target)
self.auras[spell:GetID()] = {
spell = spell,
source = source,
target = target,
lastApplied = 0,
}
end
---@param aura Bastion.Spell
---@param source? Bastion.Unit
---@param target? Bastion.Unit
function Spell:CheckAuraStatus(aura, source, target)
for id, trackedAura in pairs(self.auras) do
if aura:GetID() == id then
return true
end
end
return false
end
---@param spell Bastion.Spell
---@param source? Bastion.Unit
---@param target? Bastion.Unit
function Spell:UpdateAura(spell, source, target)
if not self.auras[spell:GetID()] then
self:TrackAura(spell, source, target)
end
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.Globals.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