forked from jeffi/Bastion
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.
986 lines
26 KiB
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
|
|
|