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.

4413 lines
133 KiB

-- Classes.lua
5 months ago
-- January 2025
local addon, ns = ...
local Hekili = _G[ addon ]
local class = Hekili.Class
local state = Hekili.State
local CommitKey = ns.commitKey
local FindUnitBuffByID, FindUnitDebuffByID = ns.FindUnitBuffByID, ns.FindUnitDebuffByID
local GetItemInfo = ns.CachedGetItemInfo
local GetResourceInfo, GetResourceKey = ns.GetResourceInfo, ns.GetResourceKey
local ResetDisabledGearAndSpells = ns.ResetDisabledGearAndSpells
local RegisterEvent = ns.RegisterEvent
local RegisterUnitEvent = ns.RegisterUnitEvent
local getSpecializationKey = ns.getSpecializationKey
local LSR = LibStub( "SpellRange-1.0" )
local insert, wipe = table.insert, table.wipe
local mt_resource = ns.metatables.mt_resource
local GetActiveLossOfControlData, GetActiveLossOfControlDataCount = C_LossOfControl.GetActiveLossOfControlData, C_LossOfControl.GetActiveLossOfControlDataCount
local GetItemCooldown = C_Item.GetItemCooldown
local GetSpellDescription, GetSpellTexture = C_Spell.GetSpellDescription, C_Spell.GetSpellTexture
5 months ago
local GetSpecialization, GetSpecializationInfo = C_SpecializationInfo.GetSpecialization, C_SpecializationInfo.GetSpecializationInfo
local GetItemSpell, GetItemCount, IsUsableItem = C_Item.GetItemSpell, C_Item.GetItemCount, C_Item.IsUsableItem
local GetSpellInfo = C_Spell.GetSpellInfo
local GetSpellLink = C_Spell.GetSpellLink
local UnitBuff, UnitDebuff = ns.UnitBuff, ns.UnitDebuff
local specTemplate = {
enabled = true,
aoe = 2,
cycle = false,
cycle_min = 6,
gcdSync = true,
nameplates = true,
petbased = false,
damage = true,
damageExpiration = 8,
damageDots = false,
damageOnScreen = true,
damageRange = 0,
damagePets = false,
-- Toggles
custom1Name = "Custom 1",
custom2Name = "Custom 2",
noFeignedCooldown = false,
abilities = {
['**'] = {
disabled = false,
toggle = "default",
clash = 0,
targetMin = 0,
targetMax = 0,
5 months ago
dotCap = 0,
boss = false
}
},
items = {
['**'] = {
disabled = false,
toggle = "default",
clash = 0,
targetMin = 0,
targetMax = 0,
boss = false,
criteria = nil
}
},
5 months ago
placeboBar = 3,
ranges = {},
settings = {},
phases = {},
cooldowns = {},
utility = {},
defensives = {},
custom1 = {},
custom2 = {},
}
ns.specTemplate = specTemplate -- for options.
local function Aura_DetectSharedAura( t, type )
if not t then return end
local finder = type == "debuff" and FindUnitDebuffByID or FindUnitBuffByID
local aura = class.auras[ t.key ]
local name, _, count, _, duration, expirationTime, caster = finder( aura.shared, aura.id )
if name then
t.count = count > 0 and count or 1
if expirationTime > 0 then
t.applied = expirationTime - duration
t.expires = expirationTime
else
t.applied = state.query_time
t.expires = state.query_time + t.duration
end
t.caster = caster
return
end
t.count = 0
t.applied = 0
t.expires = 0
t.caster = "nobody"
end
local protectedFunctions = {
-- Channels.
start = true,
tick = true,
finish = true,
-- Casts
handler = true, -- Cast finish.
impact = true, -- Projectile impact.
}
local HekiliSpecMixin = {
RegisterResource = function( self, resourceID, regen, model, meta )
local resource = GetResourceKey( resourceID )
if not resource then
Hekili:Error( "Unable to identify resource with PowerType " .. resourceID .. "." )
return
end
local r = self.resources[ resource ] or {}
r.resource = resource
r.type = resourceID
r.state = model or setmetatable( {
resource = resource,
type = resourceID,
forecast = {},
fcount = 0,
times = {},
values = {},
actual = 0,
max = 1,
active_regen = 0.001,
inactive_regen = 0.001,
last_tick = 0,
swingGen = false,
add = function( amt, overcap )
-- Bypasses forecast, useful in hooks.
if overcap then r.state.amount = r.state.amount + amt
else r.state.amount = max( 0, min( r.state.amount + amt, r.state.max ) ) end
end,
timeTo = function( x )
return state:TimeToResource( r.state, x )
end,
}, mt_resource )
r.state.regenModel = regen
r.state.meta = meta or {}
for _, func in pairs( r.state.meta ) do
setfenv( func, state )
end
if r.state.regenModel then
for _, v in pairs( r.state.regenModel ) do
v.resource = v.resource or resource
self.resourceAuras[ v.resource ] = self.resourceAuras[ v.resource ] or {}
if v.aura then
self.resourceAuras[ v.resource ][ v.aura ] = true
end
if v.channel then
self.resourceAuras[ v.resource ].casting = true
end
if v.swing then
r.state.swingGen = true
end
end
end
self.primaryResource = self.primaryResource or resource
self.resources[ resource ] = r
CommitKey( resource )
end,
RegisterTalents = function( self, talents )
for talent, id in pairs( talents ) do
self.talents[ talent ] = id
CommitKey( talent )
local hero = id[ 4 ]
if hero then
self.talents[ hero ] = id
CommitKey( hero )
id[ 4 ] = nil
end
end
end,
RegisterPvpTalents = function( self, pvp )
for talent, spell in pairs( pvp ) do
self.pvptalents[ talent ] = spell
CommitKey( talent )
end
end,
RegisterAura = function( self, aura, data )
CommitKey( aura )
local a = setmetatable( {
funcs = {}
}, {
__index = function( t, k )
if t.funcs[ k ] then return t.funcs[ k ]() end
local setup = rawget( t, "onLoad" )
if setup then
t.onLoad = nil
setup( t )
return t[ k ]
end
end
} )
a.key = aura
if not data.id then
self.pseudoAuras = self.pseudoAuras + 1
data.id = ( -1000 * self.id ) - self.pseudoAuras
end
-- default values.
data.duration = data.duration or 30
data.max_stack = data.max_stack or 1
-- This is a shared buff that can come from anyone, give it a special generator.
--[[ if data.shared then
a.generate = Aura_DetectSharedAura
end ]]
for element, value in pairs( data ) do
if type( value ) == "function" then
setfenv( value, state )
if element ~= "generate" then a.funcs[ element ] = value
else a[ element ] = value end
else
a[ element ] = value
end
class.knownAuraAttributes[ element ] = true
end
5 months ago
if data.tick_time and not data.tick_fixed then
if a.funcs.tick_time then
local original = a.funcs.tick_time
a.funcs.tick_time = setfenv( function( ... )
local val = original( ... )
return ( val or 3 ) * haste
end, state )
a.funcs.base_tick_time = original
else
local original = a.tick_time
a.funcs.tick_time = setfenv( function( ... )
return ( original or 3 ) * haste
end, state )
a.base_tick_time = original
a.tick_time = nil
end
end
self.auras[ aura ] = a
if a.id then
if a.id > 0 then
-- Hekili:ContinueOnSpellLoad( a.id, function( success )
a.onLoad = function( a )
local d = GetSpellInfo( a.id )
a.name = d and d.name
if not a.name then
for k, v in pairs( class.auraList ) do
if v == a then class.auraList[ k ] = nil end
end
Hekili.InvalidSpellIDs = Hekili.InvalidSpellIDs or {}
Hekili.InvalidSpellIDs[ a.id ] = a.name or a.key
a.id = a.key
a.name = a.name or a.key
return
end
a.desc = GetSpellDescription( a.id )
local texture = a.texture or GetSpellTexture( a.id )
if self.id > 0 then
class.auraList[ a.key ] = "|T" .. texture .. ":0|t " .. a.name
end
self.auras[ a.name ] = a
if GetSpecializationInfo( GetSpecialization() or 0 ) == self.id then
-- Copy to class table as well.
class.auras[ a.name ] = a
end
if self.pendingItemSpells[ a.name ] then
local items = self.pendingItemSpells[ a.name ]
if type( items ) == "table" then
for i, item in ipairs( items ) do
local ability = self.abilities[ item ]
ability.itemSpellKey = a.key .. "_" .. ability.itemSpellID
self.abilities[ ability.itemSpellKey ] = a
class.abilities[ ability.itemSpellKey ] = a
end
else
local ability = self.abilities[ items ]
ability.itemSpellKey = a.key .. "_" .. ability.itemSpellID
self.abilities[ ability.itemSpellKey ] = a
class.abilities[ ability.itemSpellKey ] = a
end
self.pendingItemSpells[ a.name ] = nil
self.itemPended = nil
end
end
end
self.auras[ a.id ] = a
end
if data.meta then
for k, v in pairs( data.meta ) do
if type( v ) == "function" then data.meta[ k ] = setfenv( v, state ) end
class.knownAuraAttributes[ k ] = true
end
end
if data.copy then
if type( data.copy ) ~= "table" then
self.auras[ data.copy ] = a
else
for _, key in ipairs( data.copy ) do
self.auras[ key ] = a
end
end
end
end,
RegisterAuras = function( self, auras )
for aura, data in pairs( auras ) do
self:RegisterAura( aura, data )
end
end,
RegisterPower = function( self, power, id, aura )
self.powers[ power ] = id
CommitKey( power )
if aura and type( aura ) == "table" then
self:RegisterAura( power, aura )
end
end,
RegisterPowers = function( self, powers )
for k, v in pairs( powers ) do
self.powers[ k ] = v.id
self.powers[ v.id ] = k
for token, ids in pairs( v.triggers ) do
if not self.auras[ token ] then
self:RegisterAura( token, {
id = v.id,
copy = ids
} )
end
end
end
end,
RegisterStateExpr = function( self, key, func )
setfenv( func, state )
self.stateExprs[ key ] = func
class.stateExprs[ key ] = func
CommitKey( key )
end,
RegisterStateFunction = function( self, key, func )
setfenv( func, state )
self.stateFuncs[ key ] = func
class.stateFuncs[ key ] = func
CommitKey( key )
end,
RegisterStateTable = function( self, key, data )
for _, f in pairs( data ) do
if type( f ) == "function" then
setfenv( f, state )
end
end
local meta = getmetatable( data )
if meta and meta.__index then
setfenv( meta.__index, state )
end
self.stateTables[ key ] = data
class.stateTables[ key ] = data
CommitKey( key )
end,
-- Phases are for more durable variables that should be recalculated over the course of recommendations.
-- The start/finish conditions are calculated on reset and that state is persistent between sets of recommendations.
-- Within a set of recommendations, the phase conditions are recalculated when the clock advances and/or when ability handlers are fired.
-- Notably, finish is only fired if we are currently in the phase.
RegisterPhase = function( self, key, start, finish, ... )
if start then start = setfenv( start, state ) end
if finish then finish = setfenv( finish, state ) end
self.phases[ key ] = {
activate = start,
deactivate = finish,
virtual = {},
real = {}
}
local phase = self.phases[ key ]
local n = select( "#", ... )
for i = 1, n do
local hook = select( i, ... )
if hook == "reset_precast" then
self:RegisterHook( hook, function()
local d = display or "Primary"
if phase.real[ d ] == nil then
phase.real[ d ] = false
end
local original = phase.real[ d ]
if state.time == 0 and not InCombatLockdown() then
phase.real[ d ] = false
-- Hekili:Print( format( "[ %s ] Phase '%s' set to '%s' (%s) - out of combat.", self.name or "Unspecified", key, tostring( phase.real[ d ] ), hook ) )
-- if Hekili.ActiveDebug then Hekili:Debug( "[ %s ] Phase '%s' set to '%s' (%s) - out of combat.", self.name or "Unspecified", key, tostring( phase.virtual[ display or "Primary" ] ), hook ) end
end
if not phase.real[ d ] and phase.activate() then
phase.real[ d ] = true
end
if phase.real[ d ] and phase.deactivate() then
phase.real[ d ] = false
end
--[[ if phase.real[ d ] ~= original then
if d == "Primary" then Hekili:Print( format( "Phase change for %s [ %s ] (from %s to %s).", key, d, tostring( original ), tostring( phase.real[ d ] ) ) ) end
end ]]
phase.virtual[ d ] = phase.real[ d ]
if Hekili.ActiveDebug then Hekili:Debug( "[ %s ] Phase '%s' set to '%s' (%s).", self.name or "Unspecified", key, tostring( phase.virtual[ d ] ), hook ) end
end )
else
self:RegisterHook( hook, function()
local d = display or "Primary"
local previous = phase.virtual[ d ]
if phase.virtual[ d ] ~= true and phase.activate() then
phase.virtual[ d ] = true
end
if phase.virtual[ d ] == true and phase.deactivate() then
phase.virtual[ d ] = false
end
if Hekili.ActiveDebug and phase.virtual[ d ] ~= previous then Hekili:Debug( "[ %s ] Phase '%s' set to '%s' (%s) - virtual.", self.name or "Unspecified", key, tostring( phase.virtual[ d ] ), hook ) end
end )
end
end
self:RegisterVariable( key, function()
return self.phases[ key ].virtual[ display or "Primary" ]
end )
end,
RegisterPhasedVariable = function( self, key, default, value, ... )
value = setfenv( value, state )
self.phases[ key ] = {
update = value,
virtual = {},
real = {}
}
local phase = self.phases[ key ]
local n = select( "#", ... )
if type( default ) == "function" then
phase.default = setfenv( default, state )
else
phase.default = setfenv( function() return default end, state )
end
for i = 1, n do
local hook = select( i, ... )
if hook == "reset_precast" then
self:RegisterHook( hook, function()
local d = display or "Primary"
if phase.real[ d ] == nil or ( state.time == 0 and not InCombatLockdown() ) then
phase.real[ d ] = phase.default()
end
local original = phase.real[ d ] or "nil"
phase.real[ d ] = phase.update( phase.real[ d ], phase.default() )
phase.virtual[ d ] = phase.real[ d ]
if Hekili.ActiveDebug then
Hekili:Debug( "[ %s ] Phased variable '%s' set to '%s' (%s) - was '%s'.", self.name or "Unspecified", key, tostring( phase.virtual[ display or "Primary" ] ), hook, tostring( original ) )
end
end )
else
self:RegisterHook( hook, function()
local d = display or "Primary"
local previous = phase.virtual[ d ]
phase.virtual[ d ] = phase.update( phase.virtual[ d ], phase.default() )
if Hekili.ActiveDebug and phase.virtual[ d ] ~= previous then Hekili:Debug( "[ %s ] Phased variable '%s' set to '%s' (%s) - virtual.", self.name or "Unspecified", key, tostring( phase.virtual[ display or "Primary" ] ), hook ) end
end )
end
end
self:RegisterVariable( key, function()
return self.phases[ key ].virtual[ display or "Primary" ]
end )
end,
5 months ago
RegisterGear = function( self, ... )
local arg1 = select( 1, ... )
if not arg1 then return end
5 months ago
-- If the first arg is a table, it's registering multiple items/sets
if type( arg1 ) == "table" then
for set, data in pairs( arg1 ) do
self:RegisterGear( set, data )
end
return
end
5 months ago
local arg2 = select( 2, ... )
if not arg2 then return end
-- If the first arg is a string, register it
if type( arg1 ) == "string" then
local gear = self.gear[ arg1 ] or {}
local found = false
-- If the second arg is a table, it's a tier set with auras
if type( arg2 ) == "table" then
if arg2.items then
for _, item in ipairs( arg2.items ) do
if not gear[ item ] then
table.insert( gear, item )
gear[ item ] = true
found = true
end
end
end
if arg2.auras then
-- Register auras (even if no items are found, can be useful for early patch testing).
self:RegisterAuras( arg2.auras )
end
end
-- If the second arg is a number, this is a legacy registration with a single set/item
if type( arg2 ) == "number" then
local n = select( "#", ... )
for i = 2, n do
local item = select( i, ... )
if not gear[ item ] then
table.insert( gear, item )
gear[ item ] = true
found = true
end
end
end
if found then
self.gear[ arg1 ] = gear
CommitKey( arg1 )
end
return
end
5 months ago
-- Debug print if needed
-- Hekili:Print( "|cFFFF0000[Hekili]|r Invalid input passed to RegisterGear." )
end,
5 months ago
-- Check for the set bonus based on hidden aura instead of counting the number of equipped items.
-- This may be useful for tier set items that are crafted so their item ID doesn't match.
-- The alternative is *probably* to treat sets based on bonusIDs.
RegisterSetBonus = function( self, key, spellID )
self.setBonuses[ key ] = spellID
CommitKey( key )
end,
RegisterSetBonuses = function( self, ... )
local n = select( "#", ... )
for i = 1, n, 2 do
self:RegisterSetBonus( select( i, ... ) )
end
end,
RegisterPotion = function( self, potion, data )
self.potions[ potion ] = data
data.key = potion
5 months ago
if data.items then
if type( data.items ) == "table" then
for _, key in ipairs( data.items ) do
self.potions[ key ] = data
CommitKey( key )
end
else
5 months ago
self.potions[ data.items ] = data
CommitKey( data.items )
end
end
local potionItem = Item:CreateFromItemID( data.item )
if not potionItem:IsItemEmpty() then
potionItem:ContinueOnItemLoad( function()
local name = potionItem:GetItemName() or data.name
local link = potionItem:GetItemLink() or data.link
data.name = name
data.link = link
class.potionList[ potion ] = link
return true
end )
end
CommitKey( potion )
end,
RegisterPotions = function( self, potions )
for k, v in pairs( potions ) do
self:RegisterPotion( k, v )
end
end,
RegisterRecheck = function( self, func )
self.recheck = func
end,
RegisterHook = function( self, hook, func, noState )
if not ( noState == true or hook == "COMBAT_LOG_EVENT_UNFILTERED" and noState == nil ) then
func = setfenv( func, state )
end
self.hooks[ hook ] = self.hooks[ hook ] or {}
insert( self.hooks[ hook ], func )
end,
RegisterAbility = function( self, ability, data )
CommitKey( ability )
local a = setmetatable( {
funcs = {},
}, {
__index = function( t, k )
local setup = rawget( t, "onLoad" )
if setup then
t.onLoad = nil
setup( t )
return t[ k ]
end
if t.funcs[ k ] then return t.funcs[ k ]() end
if k == "lastCast" then return state.history.casts[ t.key ] or t.realCast end
if k == "lastUnit" then return state.history.units[ t.key ] or t.realUnit end
end,
} )
a.key = ability
a.from = self.id
if not data.id then
if data.item then
class.specs[ 0 ].itemAbilities = class.specs[ 0 ].itemAbilities + 1
data.id = -100 - class.specs[ 0 ].itemAbilities
else
self.pseudoAbilities = self.pseudoAbilities + 1
data.id = -1000 * self.id - self.pseudoAbilities
end
5 months ago
a.id = data.id
end
if data.id and type( data.id ) == "function" then
if not data.copy or type( data.copy ) == "table" and #data.copy == 0 then
Hekili:Error( "RegisterAbility for %s (Specialization %d) will fail; ability has an ID function but needs to have 'copy' entries for the abilities table.", ability, self.id )
end
end
local item = data.item
if item and type( item ) == "function" then
setfenv( item, state )
item = item()
end
if data.meta then
for k, v in pairs( data.meta ) do
if type( v ) == "function" then data.meta[ k ] = setfenv( v, state ) end
end
end
-- default values.
if not data.cast then data.cast = 0 end
if not data.cooldown then data.cooldown = 0 end
if not data.recharge then data.recharge = data.cooldown end
if not data.charges then data.charges = 1 end
if data.hasteCD then
if type( data.cooldown ) == "number" and data.cooldown > 0 then data.cooldown = Hekili:Loadstring( "return " .. data.cooldown .. " * haste" ) end
if type( data.recharge ) == "number" and data.recharge > 0 then data.recharge = Hekili:Loadstring( "return " .. data.recharge .. " * haste" ) end
end
if not data.fixedCast and type( data.cast ) == "number" then
data.cast = Hekili:Loadstring( "return " .. data.cast .. " * haste" )
end
if data.toggle == "interrupts" and data.gcd == "off" and data.readyTime == state.timeToInterrupt and data.interrupt == nil then
data.interrupt = true
end
for key, value in pairs( data ) do
if type( value ) == "function" then
setfenv( value, state )
if not protectedFunctions[ key ] then a.funcs[ key ] = value
else a[ key ] = value end
data[ key ] = nil
else
a[ key ] = value
end
end
5 months ago
if ( a.velocity or a.flightTime ) and a.impact and a.isProjectile == nil then
a.isProjectile = true
end
a.realCast = 0
if item then
--[[ local name, link, _, _, _, _, _, _, _, texture = GetItemInfo( item )
a.name = name or ability
a.link = link or ability ]]
class.itemMap[ item ] = ability
-- Register the item if it doesn't already exist.
class.specs[0]:RegisterGear( ability, item )
5 months ago
if data.copy then
if type( data.copy ) == "table" then
for _, iID in ipairs( data.copy ) do
if type( iID ) == "number" and iID < 0 then class.specs[0]:RegisterGear( ability, -iID ) end
end
else
if type( data.copy ) == "number" and data.copy < 0 then class.specs[0]:RegisterGear( ability, -data.copy ) end
end
end
local actionItem = Item:CreateFromItemID( item )
if not actionItem:IsItemEmpty() then
actionItem:ContinueOnItemLoad( function( success )
local name = actionItem:GetItemName()
local link = actionItem:GetItemLink()
local texture = actionItem:GetItemIcon()
5 months ago
if name then
if not a.name or a.name == a.key then a.name = name end
if not a.link or a.link == a.key then a.link = link end
5 months ago
if not a.funcs.texture then a.texture = a.texture or texture end
if a.suffix then
a.actualName = name
a.name = a.name .. " " .. a.suffix
end
self.abilities[ ability ] = self.abilities[ ability ] or a
self.abilities[ a.name ] = self.abilities[ a.name ] or a
self.abilities[ a.link ] = self.abilities[ a.link ] or a
self.abilities[ data.id ] = self.abilities[ a.link ] or a
a.itemLoaded = GetTime()
if a.item and a.item ~= 158075 then
a.itemSpellName, a.itemSpellID = GetItemSpell( a.item )
if a.itemSpellID then
a.itemSpellKey = a.key .. "_" .. a.itemSpellID
self.abilities[ a.itemSpellKey ] = a
class.abilities[ a.itemSpellKey ] = a
end
if a.itemSpellName then
local itemAura = self.auras[ a.itemSpellName ]
if itemAura then
a.itemSpellKey = itemAura.key .. "_" .. a.itemSpellID
self.abilities[ a.itemSpellKey ] = a
class.abilities[ a.itemSpellKey ] = a
else
if self.pendingItemSpells[ a.itemSpellName ] then
if type( self.pendingItemSpells[ a.itemSpellName ] ) == "table" then
table.insert( self.pendingItemSpells[ a.itemSpellName ], ability )
else
local first = self.pendingItemSpells[ a.itemSpellName ]
self.pendingItemSpells[ a.itemSpellName ] = {
first,
ability
}
end
else
self.pendingItemSpells[ a.itemSpellName ] = ability
a.itemPended = GetTime()
end
end
end
end
if not a.unlisted then
class.abilityList[ ability ] = a.listName or ( "|T" .. ( a.texture or texture ) .. ":0|t " .. link )
class.itemList[ item ] = a.listName or ( "|T" .. a.texture .. ":0|t " .. link )
class.abilityByName[ a.name ] = a
end
if data.copy then
if type( data.copy ) == "string" or type( data.copy ) == "number" then
self.abilities[ data.copy ] = a
elseif type( data.copy ) == "table" then
for _, key in ipairs( data.copy ) do
self.abilities[ key ] = a
end
end
end
if data.items then
local addedToItemList = false
for _, id in ipairs( data.items ) do
local copyItem = Item:CreateFromItemID( id )
if not copyItem:IsItemEmpty() then
5 months ago
self:RegisterGear( a.key, id )
copyItem:ContinueOnItemLoad( function()
local name = copyItem:GetItemName()
local link = copyItem:GetItemLink()
local texture = copyItem:GetItemIcon()
if name then
class.abilities[ name ] = a
self.abilities[ name ] = a
if not class.itemList[ id ] then
class.itemList[ id ] = a.listName or ( "|T" .. ( a.texture or texture ) .. ":0|t " .. link )
addedToItemList = true
end
end
end )
end
end
if addedToItemList then
if ns.ReadKeybindings then ns.ReadKeybindings() end
end
end
if ability then class.abilities[ ability ] = a end
if a.name then class.abilities[ a.name ] = a end
if a.link then class.abilities[ a.link ] = a end
if a.id then class.abilities[ a.id ] = a end
Hekili.OptionsReady = false
return true
end
return false
end )
end
end
if a.id and a.id > 0 then
-- Hekili:ContinueOnSpellLoad( a.id, function( success )
a.onLoad = function()
local spellInfo = GetSpellInfo( a.id )
5 months ago
if spellInfo == nil then
spellInfo = GetItemInfo( a.id )
end
if spellInfo then
a.name = spellInfo.name
else
a.name = nil
end
if not a.name then
for k, v in pairs( class.abilityList ) do
if v == a then class.abilityList[ k ] = nil end
end
Hekili.InvalidSpellIDs = Hekili.InvalidSpellIDs or {}
table.insert( Hekili.InvalidSpellIDs, a.id )
Hekili:Error( "Name info not available for " .. a.id .. "." )
return
end
if not a.name then Hekili:Error( "Name info not available for " .. a.id .. "." ); return false end
a.desc = GetSpellDescription( a.id ) -- was returning raw tooltip data.
if a.suffix then
a.actualName = a.name
a.name = a.name .. " " .. a.suffix
end
local texture = a.texture or GetSpellTexture( a.id )
self.abilities[ a.name ] = self.abilities[ a.name ] or a
class.abilities[ a.name ] = class.abilities[ a.name ] or a
if not a.unlisted then
class.abilityList[ ability ] = a.listName or ( "|T" .. texture .. ":0|t " .. a.name )
class.abilityByName[ a.name ] = class.abilities[ a.name ] or a
end
if a.rangeSpell and type( a.rangeSpell ) == "number" then
Hekili:ContinueOnSpellLoad( a.rangeSpell, function( success )
if success then
local info = GetSpellInfo( a.rangeSpell )
if info then
a.rangeSpell = info.name
else
a.rangeSpell = nil
end
else
a.rangeSpell = nil
end
end )
end
Hekili.OptionsReady = false
end
end
self.abilities[ ability ] = a
self.abilities[ a.id ] = a
if not a.unlisted then class.abilityList[ ability ] = class.abilityList[ ability ] or a.listName or a.name end
if data.copy then
if type( data.copy ) == "string" or type( data.copy ) == "number" then
self.abilities[ data.copy ] = a
elseif type( data.copy ) == "table" then
for _, key in ipairs( data.copy ) do
self.abilities[ key ] = a
end
end
end
if data.items then
for _, itemID in ipairs( data.items ) do
class.itemMap[ itemID ] = ability
end
end
if a.dual_cast or a.funcs.dual_cast then
self.can_dual_cast = true
self.dual_cast[ a.key ] = true
end
if a.empowered or a.funcs.empowered then
self.can_empower = true
end
if a.auras then
self:RegisterAuras( a.auras )
end
end,
RegisterAbilities = function( self, abilities )
for ability, data in pairs( abilities ) do
self:RegisterAbility( ability, data )
end
end,
RegisterPack = function( self, name, version, import )
self.packs[ name ] = {
version = tonumber( version ),
import = import:gsub("([^|])|([^|])", "%1||%2")
}
end,
RegisterPriority = function( self, name, version, notes, priority )
end,
RegisterRanges = function( self, ... )
if type( ... ) == "table" then
self.ranges = ...
return
end
for i = 1, select( "#", ... ) do
insert( self.ranges, ( select( i, ... ) ) )
end
end,
RegisterRangeFilter = function( self, name, func )
self.filterName = name
self.filter = func
end,
RegisterOptions = function( self, options )
self.options = options
end,
RegisterEvent = function( self, event, func )
RegisterEvent( event, function( ... )
if state.spec.id == self.id then func( ... ) end
end )
end,
RegisterUnitEvent = function( self, event, unit1, unit2, func )
RegisterUnitEvent( event, unit1, unit2, function( ... )
if state.spec.id == self.id then func( ... ) end
end )
end,
RegisterCombatLogEvent = function( self, func )
self:RegisterHook( "COMBAT_LOG_EVENT_UNFILTERED", func )
end,
RegisterCycle = function( self, func )
self.cycle = setfenv( func, state )
end,
RegisterPet = function( self, token, id, spell, duration, ... )
CommitKey( token )
5 months ago
-- Prepare the main model
local model = {
id = type( id ) == "function" and setfenv( id, state ) or id,
token = token,
spell = spell,
duration = type( duration ) == "function" and setfenv( duration, state ) or duration
}
5 months ago
-- Register the main pet token
self.pets[ token ] = model
5 months ago
-- Register copies, but avoid overwriting unrelated registrations
local n = select( "#", ... )
if n and n > 0 then
for i = 1, n do
5 months ago
local alias = select( i, ... )
if self.pets[ alias ] and self.pets[ alias ] ~= model then
if Hekili.ActiveDebug then
Hekili:Debug( "RegisterPet: Alias '%s' already assigned to a different pet. Skipping for token '%s'.", tostring( alias ), tostring( token ) )
end
else
self.pets[ alias ] = model
end
end
end
end,
RegisterPets = function( self, pets )
for token, data in pairs( pets ) do
-- Extract fields from the pet definition.
local id = data.id
local spell = data.spell
local duration = data.duration
local copy = data.copy
-- Register the pet and handle the copy field if it exists.
if copy then
self:RegisterPet( token, id, spell, duration, type( copy ) == "string" and copy or unpack( copy ) )
else
self:RegisterPet( token, id, spell, duration )
end
end
end,
5 months ago
RegisterTotem = function( self, token, id, ... )
-- Register the primary totem.
self.totems[ token ] = id
self.totems[ id ] = token
5 months ago
-- Handle copies if provided.
local n = select( "#", ... )
if n and n > 0 then
for i = 1, n do
local copy = select( i, ... )
self.totems[ copy ] = id
self.totems[ id ] = copy
end
end
-- Commit the primary token.
CommitKey( token )
end,
5 months ago
RegisterTotems = function( self, totems )
for token, data in pairs( totems ) do
local id = data.id
local copy = data.copy
-- Register the primary totem.
self.totems[ token ] = id
self.totems[ id ] = token
-- Register any copies (aliases).
if copy then
if type( copy ) == "string" then
self.totems[ copy ] = id
self.totems[ id ] = copy
elseif type( copy ) == "table" then
for _, alias in ipairs( copy ) do
self.totems[ alias ] = id
self.totems[ id ] = alias
end
end
end
CommitKey( token )
end
end,
GetSetting = function( self, info )
local setting = info[ #info ]
return Hekili.DB.profile.specs[ self.id ].settings[ setting ]
end,
SetSetting = function( self, info, val )
local setting = info[ #info ]
Hekili.DB.profile.specs[ self.id ].settings[ setting ] = val
end,
-- option should be an AceOption table.
RegisterSetting = function( self, key, value, option )
CommitKey( key )
table.insert( self.settings, {
name = key,
default = value,
info = option
} )
option.order = 100 + #self.settings
option.get = option.get or function( info )
local setting = info[ #info ]
local val = Hekili.DB.profile.specs[ self.id ].settings[ setting ]
if val ~= nil then return val end
return value
end
option.set = option.set or function( info, val )
local setting = info[ #info ]
Hekili.DB.profile.specs[ self.id ].settings[ setting ] = val
end
end,
-- For faster variables.
RegisterVariable = function( self, key, func )
CommitKey( key )
self.variables[ key ] = setfenv( func, state )
end,
}
function Hekili:RestoreDefaults()
local p = self.DB.profile
local reverted = {}
local changed = {}
for k, v in pairs( class.packs ) do
local existing = rawget( p.packs, k )
if not existing or not existing.version or existing.version ~= v.version then
local data = self.DeserializeActionPack( v.import )
if data and type( data ) == "table" then
p.packs[ k ] = data.payload
data.payload.version = v.version
data.payload.date = v.version
data.payload.builtIn = true
if not existing or not existing.version or existing.version < v.version then
insert( changed, k )
else
insert( reverted, k )
end
local specID = data.payload.spec
if specID then
local spec = rawget( p.specs, specID )
if spec then
if spec.package then
local currPack = p.packs[ spec.package ]
if not currPack or currPack.spec ~= specID then
spec.package = k
end
else
spec.package = k
end
end
end
end
end
end
if #changed > 0 or #reverted > 0 then
self:LoadScripts()
end
if #changed > 0 then
local msg
if #changed == 1 then
msg = "The |cFFFFD100" .. changed[1] .. "|r priority was updated."
elseif #changed == 2 then
msg = "The |cFFFFD100" .. changed[1] .. "|r and |cFFFFD100" .. changed[2] .. "|r priorities were updated."
else
msg = "|cFFFFD100" .. changed[1] .. "|r"
for i = 2, #changed - 1 do
msg = msg .. ", |cFFFFD100" .. changed[i] .. "|r"
end
msg = "The " .. msg .. ", and |cFFFFD100" .. changed[ #changed ] .. "|r priorities were updated."
end
if msg then
C_Timer.After( 5, function()
if Hekili.DB.profile.notifications.enabled then Hekili:Notify( msg, 6 ) end
Hekili:Print( msg )
end )
end
end
if #reverted > 0 then
local msg
if #reverted == 1 then
msg = "The |cFFFFD100" .. reverted[1] .. "|r priority was reverted."
elseif #reverted == 2 then
msg = "The |cFFFFD100" .. reverted[1] .. "|r and |cFFFFD100" .. reverted[2] .. "|r priorities were reverted."
else
msg = "|cFFFFD100" .. reverted[1] .. "|r"
for i = 2, #reverted - 1 do
msg = msg .. ", |cFFFFD100" .. reverted[i] .. "|r"
end
msg = "The " .. msg .. ", and |cFFFFD100" .. reverted[ #reverted ] .. "|r priorities were reverted."
end
if msg then
C_Timer.After( 6, function()
if Hekili.DB.profile.notifications.enabled then Hekili:Notify( msg, 6 ) end
Hekili:Print( msg )
end )
end
end
end
function Hekili:RestoreDefault( name )
local p = self.DB.profile
local default = class.packs[ name ]
if default then
local data = self.DeserializeActionPack( default.import )
if data and type( data ) == "table" then
p.packs[ name ] = data.payload
data.payload.version = default.version
data.payload.date = default.version
data.payload.builtIn = true
end
end
end
ns.restoreDefaults = function( category, purge )
end
ns.isDefault = function( name, category )
if not name or not category then
return false
end
for i, default in ipairs( class.defaults ) do
if default.type == category and default.name == name then
return true, i
end
end
return false
end
function Hekili:NewSpecialization( specID, isRanged, icon )
if not specID or specID < 0 then return end
5 months ago
isRanged = isRanged or ns.Specializations[ specID ].ranged
local id, name, _, texture, role, pClass
if Hekili.IsRetail() and specID > 0 then id, name, _, texture, role, pClass = GetSpecializationInfoByID( specID )
else
id = specID
texture = icon
end
if not id then
Hekili:Error( "Unable to generate specialization DB for spec ID #" .. specID .. "." )
return nil
end
if specID ~= 0 then
class.initialized = true
end
local token = getSpecializationKey( id )
local spec = class.specs[ id ] or {
id = id,
key = token,
name = name,
texture = texture,
role = role,
class = pClass,
melee = not isRanged,
resources = {},
resourceAuras = {},
primaryResource = nil,
5 months ago
primaryStat = nil,
talents = {},
pvptalents = {},
powers = {},
auras = {},
pseudoAuras = 0,
abilities = {},
pseudoAbilities = 0,
itemAbilities = 0,
pendingItemSpells = {},
pets = {},
totems = {},
potions = {},
ranges = {},
settings = {},
stateExprs = {}, -- expressions are returned as values and take no args.
stateFuncs = {}, -- functions can take arguments and can be used as helper functions in handlers.
stateTables = {}, -- tables are... tables.
gear = {},
setBonuses = {},
hooks = {},
funcHooks = {},
phases = {},
interrupts = {},
dual_cast = {},
packs = {},
options = {},
variables = {}
}
class.num = class.num + 1
for key, func in pairs( HekiliSpecMixin ) do
spec[ key ] = func
end
class.specs[ id ] = spec
return spec
end
function Hekili:GetSpecialization( specID )
if not specID then return class.specs[ 0 ] end
return class.specs[ specID ]
end
class.file = UnitClassBase( "player" )
local all = Hekili:NewSpecialization( 0, "All", "Interface\\Addons\\Hekili\\Textures\\LOGO-WHITE.blp" )
------------------------------
-- SHARED SPELLS/BUFFS/ETC. --
------------------------------
5 months ago
---@diagnostic disable-next-line: need-check-nil
all:RegisterAuras( {
enlisted_a = {
id = 282559,
duration = 3600,
},
enlisted_b = {
id = 289954,
duration = 3600,
},
enlisted_c = {
id = 269083,
duration = 3600,
},
enlisted = {
alias = { "enlisted_c", "enlisted_b", "enlisted_a" },
aliasMode = "first",
aliasType = "buff",
duration = 3600,
},
5 months ago
-- The War Within M+ Affix auras
-- Haste
cosmic_ascension = {
id = 461910,
duration = 30,
max_stack = 1
},
-- Crit
rift_essence = {
id = 465136,
duration = 30,
max_stack = 1
},
-- Mastery
void_essence = {
id = 463767,
duration = 30,
max_stack = 1
},
-- CDR & Vers
voidbinding = {
id = 462661,
duration = 30,
max_stack = 1
},
-- Priory of the Sacred Flame
blessing_of_the_sacred_flame = {
id = 435088,
duration = 1800,
max_stack = 1
},
-- Can be used in GCD calculation.
shadowform = {
id = 232698,
duration = 3600,
max_stack = 1,
},
voidform = {
id = 194249,
duration = 15,
max_stack = 1,
},
adrenaline_rush = {
id = 13750,
duration = 20,
max_stack = 1,
},
-- Bloodlusts
ancient_hysteria = {
id = 90355,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
heroism = {
id = 32182,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
time_warp = {
id = 80353,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
netherwinds = {
id = 160452,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
primal_rage = {
id = 264667,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
drums_of_deathly_ferocity = {
id = 309658,
shared = "player", -- use anyone's buff on the player, not just player's.
duration = 40,
max_stack = 1,
},
bloodlust = {
5 months ago
alias = { "ancient_hysteria", "bloodlust_actual", "drums_of_deathly_ferocity", "fury_of_the_aspects", "heroism", "netherwinds", "primal_rage", "time_warp", "harriers_cry" },
aliasMode = "first",
aliasType = "buff",
duration = 3600,
},
bloodlust_actual = {
id = 2825,
duration = 40,
shared = "player",
max_stack = 1,
},
exhaustion = {
id = 57723,
duration = 600,
shared = "player",
max_stack = 1,
copy = 390435
},
insanity = {
id = 95809,
duration = 600,
shared = "player",
max_stack = 1
},
temporal_displacement = {
id = 80354,
duration = 600,
shared = "player",
max_stack = 1
},
fury_of_the_aspects = {
id = 390386,
duration = 40,
max_stack = 1,
shared = "player",
},
5 months ago
harriers_cry = {
id = 466904,
duration = 40,
max_stack = 1,
shared = "player"
},
mark_of_the_wild = {
id = 1126,
duration = 3600,
max_stack = 1,
shared = "player",
},
fatigued = {
id = 264689,
duration = 600,
shared = "player",
max_stack = 1
},
sated = {
alias = { "exhaustion", "fatigued", "insanity", "sated_actual", "temporal_displacement" },
aliasMode = "first",
aliasType = "debuff",
duration = 3600,
},
sated_actual = {
id = 57724,
duration = 600,
shared = "player",
max_stack = 1,
},
blessing_of_the_bronze = {
alias = {
"blessing_of_the_bronze_evoker",
"blessing_of_the_bronze_deathknight",
"blessing_of_the_bronze_demonhunter",
"blessing_of_the_bronze_druid",
"blessing_of_the_bronze_hunter",
"blessing_of_the_bronze_mage",
"blessing_of_the_bronze_monk",
"blessing_of_the_bronze_paladin",
"blessing_of_the_bronze_priest",
"blessing_of_the_bronze_rogue",
"blessing_of_the_bronze_shaman",
"blessing_of_the_bronze_warlock",
"blessing_of_the_bronze_warrior",
},
aliasType = "buff",
aliasMode = "longest"
},
-- Can always be seen and tracked by the Hunter.; Damage taken increased by $428402s4% while above $s3% health.
-- https://wowhead.com/beta/spell=257284
hunters_mark = {
id = 257284,
duration = 3600,
tick_time = 0.5,
type = "Magic",
max_stack = 1,
shared = "target"
},
chaos_brand = {
id = 1490,
duration = 3600,
type = "Magic",
max_stack = 1,
shared = "target"
},
blessing_of_the_bronze_deathknight = {
id = 381732,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_demonhunter = {
id = 381741,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_druid = {
id = 381746,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_evoker = {
id = 381748,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_hunter = {
id = 364342,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_mage = {
id = 381750,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_monk = {
id = 381751,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_paladin = {
id = 381752,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_priest = {
id = 381753,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_rogue = {
id = 381754,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_shaman = {
id = 381756,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_warlock = {
id = 381757,
duration = 3600,
max_stack = 1,
shared = "player"
},
blessing_of_the_bronze_warrior = {
id = 381758,
duration = 3600,
max_stack = 1,
shared = "player"
},
power_infusion = {
id = 10060,
duration = 20,
max_stack = 1,
shared = "player",
dot = "buff"
},
battle_shout = {
id = 6673,
duration = 3600,
max_stack = 1,
shared = "player",
dot = "buff"
},
-- Mastery increased by $w1% and auto attacks have a $h% chance to instantly strike again.
skyfury = {
id = 462854,
5 months ago
duration = 3600,
max_stack = 1,
shared = "player",
dot = "buff"
},
-- SL Season 3
decrypted_urh_cypher = {
id = 368239,
duration = 10,
max_stack = 1,
},
old_war = {
id = 188028,
duration = 25,
},
deadly_grace = {
id = 188027,
duration = 25,
},
prolonged_power = {
id = 229206,
duration = 60,
},
dextrous = {
id = 146308,
duration = 20,
},
vicious = {
id = 148903,
duration = 10,
},
-- WoD Legendaries
archmages_incandescence_agi = {
id = 177161,
duration = 10,
},
archmages_incandescence_int = {
id = 177159,
duration = 10,
},
archmages_incandescence_str = {
id = 177160,
duration = 10,
},
archmages_greater_incandescence_agi = {
id = 177172,
duration = 10,
},
archmages_greater_incandescence_int = {
id = 177176,
duration = 10,
},
archmages_greater_incandescence_str = {
id = 177175,
duration = 10,
},
maalus = {
id = 187620,
duration = 15,
},
thorasus = {
id = 187619,
duration = 15,
},
sephuzs_secret = {
id = 208052,
duration = 10,
max_stack = 1,
},
str_agi_int = {
duration = 3600,
},
stamina = {
duration = 3600,
},
attack_power_multiplier = {
duration = 3600,
},
haste = {
duration = 3600,
},
spell_power_multiplier = {
duration = 3600,
},
critical_strike = {
duration = 3600,
},
mastery = {
duration = 3600,
},
versatility = {
duration = 3600,
},
5 months ago
empowering = {
name = "Empowering",
duration = 3600,
generate = function( t )
local e = state.empowerment
local ability = class.abilities[ e.spell ]
local spell = ability and ability.key or e.spell
t.name = ability and ability.name or "Empowering"
t.count = e.start > 0 and 1 or 0
t.expires = e.hold
t.applied = e.start - 0.1
t.duration = e.hold - t.applied
t.v1 = ability and ability.id or 0
t.v2 = 0
t.v3 = 0
t.spell = spell
t.caster = "player"
if t.remains > 0 then
local timeDiff = state.now - e.start - 0.1
if Hekili.ActiveDebug then
Hekili:Debug( "Empowerment spell: %s[%.2f], unit: %s; rewinding %.2f...", t.name, t.remains, t.caster, timeDiff )
end
state.now = state.now - timeDiff
end
end,
},
casting = {
name = "Casting",
generate = function( t, auraType )
local unit = auraType == "debuff" and "target" or "player"
5 months ago
if unit == "player" and state.buff.empowering.up then
removeBuff( "casting" )
return
end
if unit == "player" or UnitCanAttack( "player", unit ) then
local spell, _, _, startCast, endCast, _, _, notInterruptible, spellID = UnitCastingInfo( unit )
if spell then
startCast = startCast / 1000
endCast = endCast / 1000
t.name = spell
t.count = 1
t.expires = endCast
t.applied = startCast
t.duration = endCast - startCast
t.v1 = spellID
t.v2 = notInterruptible and 1 or 0
t.v3 = 0
t.caster = unit
5 months ago
if unit ~= "target" then return end
5 months ago
if state.target.is_dummy then
-- Pretend that all casts by target dummies are interruptible.
if Hekili.ActiveDebug then Hekili:Debug( "Cast '%s' is fake-interruptible", spell ) end
t.v2 = 0
elseif Hekili.DB.profile.toggles.interrupts.filterCasts and class.spellFilters[ state.instance_id ] and class.interruptibleFilters and not class.interruptibleFilters[ spellID ] then
if Hekili.ActiveDebug then Hekili:Debug( "Cast '%s' not interruptible per user preference.", spell ) end
t.v2 = 1
end
return
end
spell, _, _, startCast, endCast, _, notInterruptible, spellID = UnitChannelInfo( unit )
5 months ago
startCast = ( startCast or 0 ) / 1000
endCast = ( endCast or 0 ) / 1000
local duration = endCast - startCast
5 months ago
-- Channels greater than 10 seconds are nonsense. Probably.
if spell and duration <= 10 then
t.name = spell
t.count = 1
t.expires = endCast
t.applied = startCast
5 months ago
t.duration = duration
t.v1 = spellID
t.v2 = notInterruptible and 1 or 0
t.v3 = 1 -- channeled.
t.caster = unit
if class.abilities[ spellID ] and class.abilities[ spellID ].dontChannel then
removeBuff( "casting" )
5 months ago
return
end
5 months ago
if unit ~= "target" then return end
if state.target.is_dummy then
-- Pretend that all casts by target dummies are interruptible.
if Hekili.ActiveDebug then Hekili:Debug( "Channel '%s' is fake-interruptible", spell ) end
t.v2 = 0
elseif Hekili.DB.profile.toggles.interrupts.filterCasts and class.spellFilters[ state.instance_id ] and class.interruptibleFilters and not class.interruptibleFilters[ spellID ] then
if Hekili.ActiveDebug then Hekili:Debug( "Channel '%s' not interruptible per user preference.", spell ) end
t.v2 = 1
end
return
end
end
t.name = "Casting"
t.count = 0
t.expires = 0
t.applied = 0
t.v1 = 0
t.v2 = 0
t.v3 = 0
t.caster = unit
end,
},
movement = {
duration = 5,
max_stack = 1,
generate = function ()
local m = buff.movement
if moving then
m.count = 1
m.expires = query_time + 5
m.applied = query_time
m.caster = "player"
return
end
m.count = 0
m.expires = 0
m.applied = 0
m.caster = "nobody"
end,
},
repeat_performance = {
id = 304409,
duration = 30,
max_stack = 1,
},
-- Why do we have this, again?
unknown_buff = {},
berserking = {
id = 26297,
duration = 10,
},
hyper_organic_light_originator = {
id = 312924,
duration = 6,
},
blood_fury = {
id = 20572,
duration = 15,
},
shadowmeld = {
id = 58984,
duration = 3600,
},
ferocity_of_the_frostwolf = {
id = 274741,
duration = 15,
},
might_of_the_blackrock = {
id = 274742,
duration = 15,
},
zeal_of_the_burning_blade = {
id = 274740,
duration = 15,
},
rictus_of_the_laughing_skull = {
id = 274739,
duration = 15,
},
ancestral_call = {
duration = 15,
alias = { "ferocity_of_the_frostwolf", "might_of_the_blackrock", "zeal_of_the_burning_blade", "rictus_of_the_laughing_skull" },
aliasMode = "first",
},
arcane_pulse = {
id = 260369,
duration = 12,
},
fireblood = {
id = 273104,
duration = 8,
},
out_of_range = {
generate = function ( oor )
oor.rangeSpell = rawget( oor, "rangeSpell" ) or settings.spec.rangeChecker or class.specs[ state.spec.id ].ranges[ 1 ]
if LSR.IsSpellInRange( class.abilities[ oor.rangeSpell ].name, "target" ) ~= 1 then
oor.count = 1
oor.applied = query_time
oor.expires = query_time + 3600
oor.caster = "player"
oor.v1 = oor.rangeSpell
return
end
oor.count = 0
oor.applied = 0
oor.expires = 0
oor.caster = "nobody"
end,
},
loss_of_control = {
duration = 10,
generate = function( t )
local max_events = GetActiveLossOfControlDataCount()
if max_events > 0 then
5 months ago
local spell, start, duration, remains = 0, 0, 0, 0
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and event.lockoutSchool == 0 and event.startTime and event.startTime > 0 and event.timeRemaining and event.timeRemaining > 0 and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.duration = 10
t.caster = "nobody"
t.v1 = 0
end,
},
5 months ago
disoriented = { -- Disorients (e.g., Polymorph, Dragon’s Breath, Blind)
duration = 10,
generate = function( t )
local max_events = GetActiveLossOfControlDataCount()
if max_events > 0 then
5 months ago
local spell, start, duration, remains = 0, 0, 0, 0
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and event.locType == "CONFUSE"
and event.startTime and event.startTime > 0
and event.timeRemaining and event.timeRemaining > 0
and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.duration = 10
t.caster = "nobody"
t.v1 = 0
end,
},
5 months ago
feared = {
duration = 10,
generate = function( t )
local max_events = GetActiveLossOfControlDataCount()
if max_events > 0 then
5 months ago
local spell, start, duration, remains = 0, 0, 0, 0
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and ( event.locType == "FEAR" or event.locType == "FEAR_MECHANIC" or event.locType == "HORROR" )
and event.startTime and event.startTime > 0
and event.timeRemaining and event.timeRemaining > 0
and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.duration = 10
t.caster = "nobody"
t.v1 = 0
end,
},
5 months ago
incapacitated = { -- Effects like Sap, Freezing Trap, Gouge
duration = 10,
generate = function( t )
5 months ago
local max_events = GetActiveLossOfControlDataCount()
5 months ago
if max_events > 0 then
local spell, start, duration, remains = 0, 0, 0, 0
5 months ago
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and event.locType == "STUN"
and event.startTime and event.startTime > 0
and event.timeRemaining and event.timeRemaining > 0
and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
5 months ago
t.duration = 10
t.caster = "nobody"
5 months ago
t.v1 = 0
end,
5 months ago
copy = "sapped"
},
5 months ago
rooted = {
duration = 10,
generate = function( t )
5 months ago
local max_events = GetActiveLossOfControlDataCount()
5 months ago
if max_events > 0 then
local spell, start, duration, remains = 0, 0, 0, 0
5 months ago
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and event.locType == "ROOT" and event.startTime and event.startTime > 0 and event.timeRemaining and event.timeRemaining > 0 and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
5 months ago
t.duration = 10
t.caster = "nobody"
5 months ago
t.v1 = 0
end,
},
5 months ago
snared = {
duration = 10,
generate = function( t )
5 months ago
local max_events = GetActiveLossOfControlDataCount()
5 months ago
if max_events > 0 then
local spell, start, duration, remains = 0, 0, 0, 0
5 months ago
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
5 months ago
if event and event.locType == "SNARE" and event.startTime and event.startTime > 0 and event.timeRemaining and event.timeRemaining > 0 and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
5 months ago
t.duration = 10
t.caster = "nobody"
5 months ago
t.v1 = 0
end,
5 months ago
copy = "slowed"
},
5 months ago
stunned = { -- Shorter stuns (e.g., Kidney Shot, Cheap Shot, Bash)
duration = 10,
generate = function( t )
local max_events = GetActiveLossOfControlDataCount()
if max_events > 0 then
local spell, start, duration, remains = 0, 0, 0, 0
for i = 1, max_events do
local event = GetActiveLossOfControlData( i )
if event and event.locType == "STUN_MECHANIC"
and event.startTime and event.startTime > 0
and event.timeRemaining and event.timeRemaining > 0
and event.timeRemaining > remains then
spell = event.spellID
start = event.startTime
duration = event.duration
remains = event.timeRemaining
end
end
if start + duration > query_time then
t.count = 1
t.expires = start + duration
t.applied = start
t.duration = duration
t.caster = "anybody"
t.v1 = spell
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.duration = 10
t.caster = "nobody"
t.v1 = 0
end,
},
dispellable_curse = {
generate = function( t )
local i = 1
local name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
while( name ) do
if debuffType == "Curse" then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
end
if name then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
dispellable_poison = {
generate = function( t )
local i = 1
local name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
while( name ) do
if debuffType == "Poison" then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
end
if name then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
dispellable_disease = {
generate = function( t )
local i = 1
local name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
while( name ) do
if debuffType == "Disease" then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
end
if name then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
dispellable_magic = {
generate = function( t, auraType )
if auraType == "buff" then
local i = 1
local name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
while( name ) do
if debuffType == "Magic" and canDispel then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
end
if canDispel then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
else
local i = 1
local name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
while( name ) do
if debuffType == "Magic" then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
end
if name then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
stealable_magic = {
generate = function( t )
if UnitCanAttack( "player", "target" ) then
local i = 1
local name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
while( name ) do
if debuffType == "Magic" and canDispel then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
end
if canDispel then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
reversible_magic = {
generate = function( t )
local i = 1
local name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
while( name ) do
if debuffType == "Magic" then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime = UnitDebuff( "player", i, "RAID" )
end
if name then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
dispellable_enrage = {
generate = function( t )
if UnitCanAttack( "player", "target" ) then
local i = 1
local name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
while( name ) do
if debuffType == "" and canDispel then break end
i = i + 1
name, _, count, debuffType, duration, expirationTime, _, canDispel = UnitBuff( "target", i )
end
if canDispel then
t.count = count > 0 and count or 1
t.expires = expirationTime > 0 and expirationTime or query_time + 5
t.applied = expirationTime > 0 and ( expirationTime - duration ) or query_time
t.caster = "nobody"
return
end
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
},
all_absorbs = {
duration = 15,
max_stack = 1,
-- TODO: Check if function works.
generate = function( t, auraType )
local unit = auraType == "debuff" and "target" or "player"
local amount = UnitGetTotalAbsorbs( unit )
if amount > 0 then
5 months ago
-- t.name = ABSORB
t.count = 1
t.expires = now + 10
t.applied = now - 5
t.caster = unit
return
end
t.count = 0
t.expires = 0
t.applied = 0
t.caster = "nobody"
end,
copy = "unravel_absorb"
},
5 months ago
devouring_rift = {
id = 440313,
duration = 15,
shared = "player",
max_stack = 1
}
} )
do
-- Dragonflight Potions
-- There are multiple items for each potion, and there are also Toxic potions that people may not want to use.
5 months ago
local exp_potions = {
{
name = "tempered_potion",
items = { 212971, 212970, 212969, 212265, 212264, 212263 }
},
{
name = "potion_of_unwavering_focus",
items = { 212965, 212964, 212963, 212259, 212258, 212257 }
},
{
name = "frontline_potion",
items = { 212968, 212967, 212966, 212262, 212261, 212260 }
},
{
name = "elemental_potion_of_ultimate_power",
items = { 191914, 191913, 191912, 191383, 191382, 191381 }
},
{
name = "elemental_potion_of_power",
items = { 191907, 191906, 191905, 191389, 191388, 191387 }
},
5 months ago
{
name = "algari_healing_potion",
items = { 211878, 211879, 211880 }
},
5 months ago
{
name = "cavedwellers_delight",
items = { 212242, 212243, 212244 }
}
5 months ago
}
---@diagnostic disable-next-line: need-check-nil
all:RegisterAura( "fake_potion", {
duration = 30,
max_stack = 1,
} )
5 months ago
local first_potion, first_potion_key
local potion_items = {}
all:RegisterHook( "reset_precast", function ()
wipe( potion_items )
for _, potion in ipairs( exp_potions ) do
for _, item in ipairs( potion.items ) do
5 months ago
if GetItemCount( item, false ) > 0 then
potion_items[ potion.name ] = item
break
end
end
end
5 months ago
end )
all:RegisterAura( "potion", {
alias = { "fake_potion" },
aliasMode = "first",
aliasType = "buff",
duration = 30
} )
local GetItemInfo = C_Item.GetItemInfo
for _, potion in ipairs( exp_potions ) do
local potionItem = Item:CreateFromItemID( potion.items[ #potion.items ] )
-- all:RegisterAbility( potion.name, {} ) -- Create stub.
potionItem:ContinueOnItemLoad( function()
all:RegisterAbility( potion.name, {
name = potionItem:GetItemName(),
listName = potionItem:GetItemLink(),
cast = 0,
cooldown = 300,
gcd = "off",
startsCombat = false,
toggle = "potions",
item = function ()
return potion_items[ potion.name ] or potion.items[ #potion.items ]
end,
items = potion.items,
bagItem = true,
texture = potionItem:GetItemIcon(),
5 months ago
handler = function ()
applyBuff( potion.name )
end,
} )
class.abilities[ potion.name ] = all.abilities[ potion.name ]
class.potions[ potion.name ] = {
name = potionItem:GetItemName(),
link = potionItem:GetItemLink(),
item = potion.items[ #potion.items ]
}
class.potionList[ potion.name ] = "|T" .. potionItem:GetItemIcon() .. ":0|t |cff00ccff[" .. potionItem:GetItemName() .. "]|r"
for i, item in ipairs( potion.items ) do
if not first_potion then
first_potion_key = potion.name
first_potion = item
end
local each_potion = Item:CreateFromItemID( item )
if not each_potion:IsItemEmpty() then
each_potion:ContinueOnItemLoad( function()
local _, spell = GetItemSpell( item )
if not spell then Hekili:Error( "No spell found for item %d.", item ) return false end
if not all.auras[ potion.name ] then
all:RegisterAura( potion.name, {
id = spell,
duration = 30,
max_stack = 1,
copy = { spell }
} )
class.auras[ spell ] = all.auras[ potion.name ]
else
local existing = all.auras[ potion.name ]
if not existing.copy then existing.copy = {} end
insert( existing.copy, spell )
all.auras[ spell ] = all.auras[ potion.name ]
class.auras[ spell ] = all.auras[ potion.name ]
end
return true
end )
else
Hekili:Error( "Item %d is empty.", item )
end
end
end )
end
all:RegisterAbility( "potion", {
name = "Potion",
listName = '|T136243:0|t |cff00ccff[Potion]|r',
cast = 0,
cooldown = 300,
gcd = "off",
startsCombat = false,
toggle = "potions",
5 months ago
consumable = function() return state.args.potion or settings.potion or first_potion_key or "tempered_potion" end,
item = function()
if state.args.potion and class.abilities[ state.args.potion ] then return class.abilities[ state.args.potion ].item end
if spec.potion and class.abilities[ spec.potion ] then return class.abilities[ spec.potion ].item end
if first_potion and class.abilities[ first_potion ] then return class.abilities[ first_potion ].item end
return 191387
end,
bagItem = true,
handler = function ()
5 months ago
local use = all.abilities.potion
use = use and use.consumable
5 months ago
if use and use ~= "global_cooldown" then
class.abilities[ use ].handler()
setCooldown( use, action[ use ].cooldown )
end
end,
usable = function ()
5 months ago
return potion_items[ all.abilities.potion.item ], "no valid potions found in inventory"
end,
5 months ago
copy = "potion_default"
} )
end
local gotn_classes = {
WARRIOR = 28880,
MONK = 121093,
DEATHKNIGHT = 59545,
SHAMAN = 59547,
HUNTER = 59543,
PRIEST = 59544,
MAGE = 59548,
PALADIN = 59542,
ROGUE = 370626
}
local baseClass = UnitClassBase( "player" ) or "WARRIOR"
all:RegisterAura( "gift_of_the_naaru", {
id = gotn_classes[ baseClass ],
duration = 5,
max_stack = 1,
copy = { 28800, 121093, 59545, 59547, 59543, 59544, 59548, 59542, 370626 }
} )
all:RegisterAbility( "gift_of_the_naaru", {
id = gotn_classes[ baseClass ],
cast = 0,
cooldown = 180,
gcd = "off",
handler = function ()
applyBuff( "gift_of_the_naaru" )
end,
} )
all:RegisterAbilities( {
global_cooldown = {
id = 61304,
cast = 0,
cooldown = 0,
gcd = "spell",
unlisted = true,
known = function () return true end,
},
ancestral_call = {
id = 274738,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
-- usable = function () return race.maghar_orc end,
handler = function ()
applyBuff( "ancestral_call" )
end,
},
arcane_pulse = {
id = 260364,
cast = 0,
cooldown = 180,
gcd = "spell",
toggle = "cooldowns",
-- usable = function () return race.nightborne end,
handler = function ()
applyDebuff( "target", "arcane_pulse" )
end,
},
berserking = {
id = 26297,
cast = 0,
cooldown = 180,
gcd = "off",
toggle = "cooldowns",
-- usable = function () return race.troll end,
handler = function ()
applyBuff( "berserking" )
end,
},
hyper_organic_light_originator = {
id = 312924,
cast = 0,
cooldown = 180,
gcd = "off",
toggle = "defensives",
handler = function ()
applyBuff( "hyper_organic_light_originator" )
end
},
bag_of_tricks = {
id = 312411,
cast = 0,
cooldown = 90,
gcd = "spell",
toggle = "cooldowns",
},
haymaker = {
id = 287712,
cast = 1,
cooldown = 150,
gcd = "spell",
handler = function ()
if not target.is_boss then applyDebuff( "target", "haymaker" ) end
end,
auras = {
haymaker = {
id = 287712,
duration = 3,
max_stack = 1,
},
}
}
} )
-- Blood Fury spell IDs vary by class (whether you need AP/Int/both).
local bf_classes = {
DEATHKNIGHT = 20572,
HUNTER = 20572,
MAGE = 33702,
MONK = 33697,
ROGUE = 20572,
SHAMAN = 33697,
WARLOCK = 33702,
WARRIOR = 20572,
PRIEST = 33702
}
all:RegisterAbilities( {
blood_fury = {
id = function () return bf_classes[ class.file ] or 20572 end,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
-- usable = function () return race.orc end,
handler = function ()
applyBuff( "blood_fury", 15 )
end,
copy = { 33702, 20572, 33697 },
},
arcane_torrent = {
id = function ()
if class.file == "PALADIN" then return 155145 end
if class.file == "MONK" then return 129597 end
if class.file == "DEATHKNIGHT" then return 50613 end
if class.file == "WARRIOR" then return 69179 end
if class.file == "ROGUE" then return 25046 end
if class.file == "HUNTER" then return 80483 end
if class.file == "DEMONHUNTER" then return 202719 end
if class.file == "PRIEST" then return 232633 end
return 28730
end,
cast = 0,
cooldown = 120,
gcd = "spell",
-- It does start combat if there are enemies in range, but we often use it precombat for resources.
startsCombat = false,
-- usable = function () return race.blood_elf end,
toggle = "cooldowns",
handler = function ()
if class.file == "DEATHKNIGHT" then gain( 20, "runic_power" )
elseif class.file == "HUNTER" then gain( 15, "focus" )
elseif class.file == "MONK" then gain( 1, "chi" )
elseif class.file == "PALADIN" then gain( 1, "holy_power" )
elseif class.file == "ROGUE" then gain( 15, "energy" )
elseif class.file == "WARRIOR" then gain( 15, "rage" )
elseif class.file == "DEMONHUNTER" then gain( 15, "fury" )
elseif class.file == "PRIEST" and state.spec.shadow then gain( 15, "insanity" ) end
removeBuff( "dispellable_magic" )
end,
copy = { 155145, 129597, 50613, 69179, 25046, 80483, 202719, 232633 }
},
will_to_survive = {
id = 59752,
cast = 0,
cooldown = 180,
gcd = "off",
toggle = "defensives",
},
shadowmeld = {
id = 58984,
cast = 0,
cooldown = 120,
gcd = "off",
usable = function ()
if not boss or solo then return false, "requires boss fight or group (to avoid resetting)" end
if moving then return false, "can't shadowmeld while moving" end
return true
end,
handler = function ()
applyBuff( "shadowmeld" )
end,
},
lights_judgment = {
id = 255647,
cast = 0,
cooldown = 150,
gcd = "spell",
-- usable = function () return race.lightforged_draenei end,
toggle = "cooldowns",
},
stoneform = {
id = 20594,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "defensives",
buff = function()
local aura, remains = "dispellable_poison", buff.dispellable_poison.remains
for _, effect in pairs( { "dispellable_disease", "dispellable_curse", "dispellable_magic", "dispellable_bleed" } ) do
local rem = buff[ effect ].remains
if rem > remains then
aura = effect
remains = rem
end
end
return aura
end,
handler = function ()
removeBuff( "dispellable_poison" )
removeBuff( "dispellable_disease" )
removeBuff( "dispellable_curse" )
removeBuff( "dispellable_magic" )
removeBuff( "dispellable_bleed" )
applyBuff( "stoneform" )
end,
auras = {
stoneform = {
id = 65116,
duration = 8,
max_stack = 1
}
}
},
fireblood = {
id = 265221,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
-- usable = function () return race.dark_iron_dwarf end,
handler = function () applyBuff( "fireblood" ) end,
},
-- INTERNAL HANDLERS
call_action_list = {
name = "|cff00ccff[Call Action List]|r",
listName = '|T136243:0|t |cff00ccff[Call Action List]|r',
cast = 0,
cooldown = 0,
gcd = "off",
essential = true,
},
run_action_list = {
name = "|cff00ccff[Run Action List]|r",
listName = '|T136243:0|t |cff00ccff[Run Action List]|r',
cast = 0,
cooldown = 0,
gcd = "off",
essential = true,
},
wait = {
name = "|cff00ccff[Wait]|r",
listName = '|T136243:0|t |cff00ccff[Wait]|r',
cast = 0,
cooldown = 0,
gcd = "off",
essential = true,
},
pool_resource = {
name = "|cff00ccff[Pool Resource]|r",
listName = "|T136243:0|t |cff00ccff[Pool Resource]|r",
cast = 0,
cooldown = 0,
gcd = "off",
},
cancel_action = {
name = "|cff00ccff[Cancel Action]|r",
listName = "|T136243:0|t |cff00ccff[Cancel Action]|r",
cast = 0,
cooldown = 0,
gcd = "off",
usable = function ()
local a = args.action_name
local ability = class.abilities[ a ]
if not a or not ability then return false, "no action identified" end
if buff.casting.down or buff.casting.v3 ~= 1 then return false, "not channeling" end
if buff.casting.v1 ~= ability.id then return false, "not channeling " .. a end
return true
end,
timeToReady = function () return gcd.remains end,
},
variable = {
name = "|cff00ccff[Variable]|r",
listName = '|T136243:0|t |cff00ccff[Variable]|r',
cast = 0,
cooldown = 0,
gcd = "off",
essential = true,
},
healthstone = {
5 months ago
name = "Healthstone",
listName = "|T538745:0|t |cff00ccff[Healthstone]|r",
cast = 0,
cooldown = function () return time > 0 and 3600 or 60 end,
gcd = "off",
5 months ago
item = function() return talent.pact_of_gluttony.enabled and 224464 or 5512 end,
items = { 224464, 5512 },
bagItem = true,
startsCombat = false,
5 months ago
texture = function() return talent.pact_of_gluttony.enabled and 538744 or 538745 end,
usable = function ()
5 months ago
local item = talent.pact_of_gluttony.enabled and 224464 or 5512
if GetItemCount( item ) == 0 then return false, "requires healthstone in bags"
elseif not IsUsableItem( item ) then return false, "healthstone on CD"
elseif health.current >= health.max then return false, "must be damaged" end
return true
end,
readyTime = function ()
5 months ago
local start, duration = GetItemCooldown( talent.pact_of_gluttony.enabled and 224464 or 5512 )
return max( 0, start + duration - query_time )
end,
handler = function ()
gain( 0.25 * health.max, "health" )
end,
},
weyrnstone = {
name = function () return ( GetItemInfo( 205146 ) ) or "Weyrnstone" end,
listName = function ()
local _, link, _, _, _, _, _, _, _, tex = GetItemInfo( 205146 )
if link and tex then return "|T" .. tex .. ":0|t " .. link end
return "|cff00ccff[Weyrnstone]|r"
end,
cast = 1.5,
cooldown = 120,
gcd = "spell",
item = 205146,
bagItem = true,
startsCombat = false,
texture = 5199618,
usable = function ()
if GetItemCount( 205146 ) == 0 then return false, "requires weyrnstone in bags" end
if solo then return false, "must have an ally to teleport" end
return true
end,
readyTime = function ()
local start, duration = GetItemCooldown( 205146 )
return max( 0, start + duration - query_time )
end,
handler = function ()
end,
copy = { "use_weyrnstone", "active_weyrnstone" }
},
cancel_buff = {
name = "|cff00ccff[Cancel Buff]|r",
listName = '|T136243:0|t |cff00ccff[Cancel Buff]|r',
cast = 0,
gcd = "off",
startsCombat = false,
buff = function () return args.buff_name or nil end,
indicator = "cancel",
texture = function ()
if not args.buff_name then return 134400 end
local a = class.auras[ args.buff_name ]
-- if not a then return 134400 end
if a.texture then return a.texture end
a = a and a.id
a = a and GetSpellTexture( a )
return a or 134400
end,
usable = function () return args.buff_name ~= nil, "no buff name detected" end,
timeToReady = function () return gcd.remains end,
handler = function ()
if not args.buff_name then return end
local cancel = args.buff_name and buff[ args.buff_name ]
cancel = cancel and rawget( cancel, "onCancel" )
if cancel then
cancel()
return
end
removeBuff( args.buff_name )
end,
},
null_cooldown = {
name = "|cff00ccff[Null Cooldown]|r",
listName = "|T136243:0|t |cff00ccff[Null Cooldown]|r",
cast = 0,
cooldown = 0.001,
gcd = "off",
startsCombat = false,
unlisted = true
},
trinket1 = {
name = "|cff00ccff[Trinket #1]|r",
listName = "|T136243:0|t |cff00ccff[Trinket #1]|r",
cast = 0,
cooldown = 600,
gcd = "off",
usable = false,
copy = "actual_trinket1",
},
trinket2 = {
name = "|cff00ccff[Trinket #2]|r",
listName = "|T136243:0|t |cff00ccff[Trinket #2]|r",
cast = 0,
cooldown = 600,
gcd = "off",
usable = false,
copy = "actual_trinket2",
},
main_hand = {
name = "|cff00ccff[" .. INVTYPE_WEAPONMAINHAND .. "]|r",
listName = "|T136243:0|t |cff00ccff[" .. INVTYPE_WEAPONMAINHAND .. "]|r",
cast = 0,
cooldown = 600,
gcd = "off",
usable = false,
copy = "actual_main_hand",
}
} )
-- Use Items
do
-- Should handle trinkets/items internally.
-- 1. Check APLs and don't try to recommend items that have their own APL entries.
-- 2. Respect item preferences registered in spec options.
all:RegisterAbility( "use_items", {
name = "Use Items",
listName = "|T136243:0|t |cff00ccff[Use Items]|r",
cast = 0,
cooldown = 120,
gcd = "off",
} )
all:RegisterAbility( "unusable_trinket", {
name = "Unusable Trinket",
listName = "|T136240:0|t |cff00ccff[Unusable Trinket]|r",
cast = 0,
cooldown = 180,
gcd = "off",
usable = false,
unlisted = true
} )
all:RegisterAbility( "heart_essence", {
name = function () return ( GetItemInfo( 158075 ) ) or "Heart Essence" end,
listName = function ()
local _, link, _, _, _, _, _, _, _, tex = GetItemInfo( 158075 )
if link and tex then return "|T" .. tex .. ":0|t " .. link end
return "|cff00ccff[Heart Essence]|r"
end,
cast = 0,
cooldown = 0,
gcd = "off",
item = 158075,
essence = true,
toggle = "essences",
usable = function () return false, "your equipped major essence is supported elsewhere in the priority or is not an active ability" end
} )
end
-- x.x - Heirloom Trinket(s)
all:RegisterAbility( "touch_of_the_void", {
cast = 0,
cooldown = 120,
gcd = "off",
item = 128318,
toggle = "cooldowns",
} )
5 months ago
-- PvP Trinkets
-- Medallions
do
local pvp_medallions = {
{ "dread_aspirants_medallion", 162897 },
{ "dread_gladiators_medallion", 161674 },
{ "sinister_aspirants_medallion", 165220 },
{ "sinister_gladiators_medallion", 165055 },
{ "notorious_aspirants_medallion", 167525 },
{ "notorious_gladiators_medallion", 167377 },
{ "old_corrupted_gladiators_medallion", 172666 },
{ "corrupted_aspirants_medallion", 184058 },
{ "corrupted_gladiators_medallion", 184055 },
{ "sinful_aspirants_medallion", 184052 },
{ "sinful_gladiators_medallion", 181333 },
{ "unchained_aspirants_medallion", 185309 },
{ "unchained_gladiators_medallion", 185304 },
{ "cosmic_aspirants_medallion", 186966 },
{ "cosmic_gladiators_medallion", 186869 },
{ "eternal_aspirants_medallion", 192412 },
{ "eternal_gladiators_medallion", 192298 },
{ "obsidian_combatants_medallion", 204164 },
{ "obsidian_aspirants_medallion", 205779 },
{ "obsidian_gladiators_medallion", 205711 },
{ "forged_aspirants_medallion", 218422 },
{ "forged_gladiators_medallion", 218716 }
}
5 months ago
local pvp_medallions_copy = {}
5 months ago
for _, v in ipairs( pvp_medallions ) do
insert( pvp_medallions_copy, v[1] )
all:RegisterGear( v[1], v[2] )
all:RegisterGear( "gladiators_medallion", v[2] )
end
5 months ago
all:RegisterAbility( "gladiators_medallion", {
name = function ()
local data = GetSpellInfo( 277179 )
return data and data.name or "Gladiator's Medallion"
end,
listName = function ()
local data = GetSpellInfo( 277179 )
if data and data.iconID then return "|T" .. data.iconID .. ":0|t " .. ( GetSpellLink( 277179 ) ) end
end,
link = function () return ( GetSpellLink( 277179 ) ) end,
cast = 0,
cooldown = 120,
gcd = "off",
5 months ago
item = function ()
local m
for _, medallion in ipairs( pvp_medallions ) do
m = medallion[ 2 ]
if equipped[ m ] then return m end
end
return m
end,
items = { 161674, 162897, 165055, 165220, 167377, 167525, 181333, 184052, 184055, 172666, 184058, 185309, 185304, 186966, 186869, 192412, 192298, 204164, 205779, 205711, 205779, 205711, 218422, 218716 },
toggle = "defensives",
5 months ago
usable = function () return debuff.loss_of_control.up, "requires loss of control effect" end,
5 months ago
handler = function ()
applyBuff( "gladiators_medallion" )
end,
5 months ago
copy = pvp_medallions_copy
} )
5 months ago
all:RegisterAura( "gladiators_medallion", {
id = 277179,
duration = 20,
max_stack = 1
5 months ago
} )
end
5 months ago
-- Badges
do
local pvp_badges = {
{ "dread_aspirants_badge", 162966 },
{ "dread_gladiators_badge", 161902 },
{ "sinister_aspirants_badge", 165223 },
{ "sinister_gladiators_badge", 165058 },
{ "notorious_aspirants_badge", 167528 },
{ "notorious_gladiators_badge", 167380 },
{ "corrupted_aspirants_badge", 172849 },
{ "corrupted_gladiators_badge", 172669 },
{ "sinful_aspirants_badge_of_ferocity", 175884 },
{ "sinful_gladiators_badge_of_ferocity", 175921 },
{ "unchained_aspirants_badge_of_ferocity", 185161 },
{ "unchained_gladiators_badge_of_ferocity", 185197 },
{ "cosmic_aspirants_badge_of_ferocity", 186906 },
{ "cosmic_gladiators_badge_of_ferocity", 186866 },
{ "eternal_aspirants_badge_of_ferocity", 192352 },
{ "eternal_gladiators_badge_of_ferocity", 192295 },
{ "crimson_aspirants_badge_of_ferocity", 201449 },
{ "crimson_gladiators_badge_of_ferocity", 201807 },
{ "obsidian_aspirants_badge_of_ferocity", 205778 },
{ "obsidian_gladiator_badge_of_ferocity", 205708 },
{ "verdant_aspirants_badge_of_ferocity", 209763 },
{ "verdant_gladiators_badge_of_ferocity", 209343 },
{ "forged_aspirants_badge_of_ferocity", 218421 },
{ "forged_gladiators_badge_of_ferocity", 218713 },
{ "prized_aspirants_badge_of_ferocity", 229491 },
{ "prized_gladiators_badge_of_ferocity", 229780 }
}
5 months ago
local pvp_badges_copy = {}
5 months ago
for _, v in ipairs( pvp_badges ) do
insert( pvp_badges_copy, v[1] )
all:RegisterGear( v[1], v[2] )
all:RegisterGear( "gladiators_badge", v[2] )
end
5 months ago
all:RegisterAbility( "gladiators_badge", {
name = function ()
local data = GetSpellInfo( 277185 )
return data and data.name or "Gladiator's Badge"
end,
listName = function ()
local data = GetSpellInfo( 277185 )
if data and data.iconID then return "|T" .. data.iconID .. ":0|t " .. ( GetSpellLink( 277185 ) ) end
end,
link = function () return ( GetSpellLink( 277185 ) ) end,
cast = 0,
5 months ago
cooldown = 120,
gcd = "off",
5 months ago
items = { 162966, 161902, 165223, 165058, 167528, 167380, 172849, 172669, 175884, 175921, 185161, 185197, 186906, 186866, 192352, 192295, 201449, 201807, 205778, 205708, 209763, 209343, 218421, 218713, 229491, 229780 },
texture = 135884,
5 months ago
toggle = "cooldowns",
item = function ()
local b
5 months ago
for i = #pvp_badges, 1, -1 do
b = pvp_badges[ i ][ 2 ]
if equipped[ b ] then
break
end
end
5 months ago
return b
end,
5 months ago
usable = function () return set_bonus.gladiators_badge > 0, "requires Gladiator's Badge" end,
handler = function ()
applyBuff( "gladiators_badge" )
end,
5 months ago
copy = pvp_badges_copy
} )
5 months ago
all:RegisterAura( "gladiators_badge", {
id = 277185,
duration = 15,
max_stack = 1
} )
end
5 months ago
-- Insignias -- N/A, not on-use.
all:RegisterAura( "gladiators_insignia", {
id = 277181,
duration = 20,
5 months ago
max_stack = 1,
copy = 345230
} )
5 months ago
-- Safeguard (equipped, not on-use)
all:RegisterAura( "gladiators_safeguard", {
id = 286342,
duration = 10,
max_stack = 1
} )
5 months ago
-- Emblems
do
local pvp_emblems = {
-- dread_combatants_emblem = 161812,
dread_aspirants_emblem = 162898,
dread_gladiators_emblem = 161675,
sinister_aspirants_emblem = 165221,
sinister_gladiators_emblem = 165056,
notorious_gladiators_emblem = 167378,
notorious_aspirants_emblem = 167526,
corrupted_gladiators_emblem = 172667,
corrupted_aspirants_emblem = 172847,
sinful_aspirants_emblem = 178334,
sinful_gladiators_emblem = 178447,
unchained_aspirants_emblem = 185242,
unchained_gladiators_emblem = 185282,
cosmic_aspirants_emblem = 186946,
cosmic_gladiators_emblem = 186868,
eternal_aspirants_emblem = 192392,
eternal_gladiators_emblem = 192297,
crimson_aspirants_emblem = 201452,
crimson_gladiators_emblem = 201809,
obsidian_combatants_emblem = 204166,
obsidian_aspirants_emblem = 205781,
obsidian_gladiators_emblem = 205710,
verdant_aspirants_emblem = 209766,
verdant_combatants_emblem = 208309,
verdant_gladiators_emblem = 209345,
algari_competitors_emblem = 219933,
forged_gladiators_emblem = 218715,
prized_aspirants_emblem = 229494,
prized_gladiators_emblem = 229782
}
5 months ago
local pvp_emblems_copy = {}
5 months ago
for k, v in pairs( pvp_emblems ) do
insert( pvp_emblems_copy, k )
all:RegisterGear( k, v )
all:RegisterGear( "gladiators_emblem", v )
end
5 months ago
all:RegisterAbility( "gladiators_emblem", {
name = function ()
local data = GetSpellInfo( 277187 )
return data and data.name or "Gladiator's Emblem"
end,
5 months ago
listName = function ()
local data = GetSpellInfo( 277187 )
if data and data.iconID then return "|T" .. data.iconID .. ":0|t " .. ( GetSpellLink( 277187 ) ) end
end,
link = function () return ( GetSpellLink( 277187 ) ) end,
cast = 0,
5 months ago
cooldown = 90,
gcd = "off",
5 months ago
item = function ()
local e
for _, emblem in pairs( pvp_emblems ) do
e = emblem
if equipped[ e ] then return e end
end
return e
end,
items = { 162898, 161675, 165221, 165056, 167378, 167526, 172667, 172847, 178334, 178447, 185242, 185282, 186946, 186868, 192392, 192297, 201452, 201809, 204166, 205781, 205710, 209766, 208309, 209345, 219933, 218715, 229494, 229782 },
toggle = "cooldowns",
handler = function ()
5 months ago
applyBuff( "gladiators_emblem" )
end,
5 months ago
copy = pvp_emblems_copy
} )
5 months ago
all:RegisterAura( "gladiators_emblem", {
id = 277187,
duration = 15,
max_stack = 1,
} )
end
5 months ago
-- 8.3 Corrupted On-Use
5 months ago
-- DNI, because potentially you have no enemies w/ Corruption w/in range.
--[[
all:RegisterAbility( "corrupted_gladiators_breach", {
cast = 0,
cooldown = 120,
gcd = "off",
5 months ago
item = 174276,
toggle = "defensives",
handler = function ()
5 months ago
applyBuff( "void_jaunt" )
-- +Debuff?
end,
auras = {
5 months ago
void_jaunt = {
id = 314517,
duration = 6,
max_stack = 1,
}
}
5 months ago
} )
]]
5 months ago
all:RegisterAbility( "corrupted_gladiators_spite", {
cast = 0,
cooldown = 60,
gcd = "off",
5 months ago
item = 174472,
toggle = "cooldowns",
5 months ago
handler = function ()
applyDebuff( "target", "gladiators_spite" )
applyDebuff( "target", "lingering_spite" )
end,
5 months ago
auras = {
gladiators_spite = {
id = 315391,
duration = 15,
max_stack = 1,
},
lingering_spite = {
id = 320297,
duration = 3600,
max_stack = 1,
}
5 months ago
}
} )
5 months ago
all:RegisterAbility( "corrupted_gladiators_maledict", {
cast = 0,
cooldown = 120,
gcd = "off", -- ???
5 months ago
item = 172672,
toggle = "cooldowns",
5 months ago
handler = function ()
applyDebuff( "target", "gladiators_maledict" )
end,
5 months ago
auras = {
gladiators_maledict = {
id = 305252,
duration = 6,
max_stack = 1
}
5 months ago
}
} )
5 months ago
-- BREWFEST
all:RegisterAbility( "brawlers_statue", {
cast = 0,
cooldown = 120,
gcd = "off",
5 months ago
item = 117357,
toggle = "defensives",
5 months ago
handler = function ()
applyBuff( "drunken_evasiveness" )
end
} )
5 months ago
all:RegisterAura( "drunken_evasiveness", {
id = 127967,
duration = 20,
max_stack = 1
} )
5 months ago
-- HALLOW'S END
all:RegisterAbility( "the_horsemans_sinister_slicer", {
cast = 0,
cooldown = 600,
gcd = "off",
5 months ago
item = 117356,
toggle = "cooldowns",
} )
ns.addToggle = function( name, default, optionName, optionDesc )
table.insert( class.toggles, {
name = name,
state = default,
option = optionName,
oDesc = optionDesc
} )
if Hekili.DB.profile[ 'Toggle State: ' .. name ] == nil then
Hekili.DB.profile[ 'Toggle State: ' .. name ] = default
end
end
ns.addSetting = function( name, default, options )
table.insert( class.settings, {
name = name,
state = default,
option = options
} )
if Hekili.DB.profile[ 'Class Option: ' .. name ] == nil then
Hekili.DB.profile[ 'Class Option: ' ..name ] = default
end
end
ns.addWhitespace = function( name, size )
table.insert( class.settings, {
name = name,
option = {
name = " ",
type = "description",
desc = " ",
width = size
}
} )
end
ns.addHook = function( hook, func )
5 months ago
insert( class.hooks[ hook ], func )
end
do
local inProgress = {}
5 months ago
local vars = {}
5 months ago
local function load_args( ... )
local count = select( "#", ... )
if count == 0 then return end
5 months ago
for i = 1, count do
vars[ i ] = select( i, ... )
end
end
5 months ago
ns.callHook = function( event, ... )
if not class.hooks[ event ] or inProgress[ event ] then return ... end
wipe( vars )
load_args( ... )
inProgress[ event ] = true
for i, hook in ipairs( class.hooks[ event ] ) do
load_args( hook( unpack( vars ) ) )
end
5 months ago
inProgress[ event ] = nil
5 months ago
return unpack( vars )
end
end
ns.registerCustomVariable = function( var, default )
state[ var ] = default
end
ns.setClass = function( name )
-- deprecated.
--class.file = name
end
function ns.setRange( value )
class.range = value
end
local function storeAbilityElements( key, values )
local ability = class.abilities[ key ]
if not ability then
ns.Error( "storeAbilityElements( " .. key .. " ) - no such ability in abilities table." )
return
end
for k, v in pairs( values ) do
ability.elem[ k ] = type( v ) == "function" and setfenv( v, state ) or v
end
end
ns.storeAbilityElements = storeAbilityElements
local function modifyElement( t, k, elem, value )
local entry = class[ t ][ k ]
if not entry then
ns.Error( "modifyElement() - no such key '" .. k .. "' in '" .. t .. "' table." )
return
end
if type( value ) == "function" then
entry.mods[ elem ] = setfenv( value, Hekili.State )
else
entry.elem[ elem ] = value
end
end
ns.modifyElement = modifyElement
local function setUsableItemCooldown( cd )
state.setCooldown( "usable_items", cd or 10 )
end
-- For Trinket Settings.
class.itemSettings = {}
local function addItemSettings( key, itemID, options )
options = options or {}
--[[ options.icon = {
type = "description",
name = function () return select( 2, GetItemInfo( itemID ) ) or format( "[%d]", itemID ) end,
order = 1,
image = function ()
local tex = select( 10, GetItemInfo( itemID ) )
if tex then
return tex, 50, 50
end
return nil
end,
imageCoords = { 0.1, 0.9, 0.1, 0.9 },
width = "full",
fontSize = "large"
} ]]
options.disabled = {
type = "toggle",
name = function () return format( "Disable %s via |cff00ccff[Use Items]|r", select( 2, GetItemInfo( itemID ) ) or ( "[" .. itemID .. "]" ) ) end,
desc = function( info )
local output = "If disabled, the addon will not recommend this item via the |cff00ccff[Use Items]|r action. " ..
"You can still manually include the item in your action lists with your own tailored criteria."
return output
end,
order = 25,
width = "full"
}
options.minimum = {
type = "range",
name = "Minimum Targets",
desc = "The addon will only recommend this trinket (via |cff00ccff[Use Items]|r) when there are at least this many targets available to hit.",
order = 26,
width = "full",
min = 1,
max = 10,
step = 1
}
options.maximum = {
type = "range",
name = "Maximum Targets",
desc = "The addon will only recommend this trinket (via |cff00ccff[Use Items]|r) when there are no more than this many targets detected.\n\n" ..
"This setting is ignored if set to 0.",
order = 27,
width = "full",
min = 0,
max = 10,
step = 1
}
class.itemSettings[ itemID ] = {
key = key,
name = function () return select( 2, GetItemInfo( itemID ) ) or ( "[" .. itemID .. "]" ) end,
item = itemID,
options = options,
}
end
--[[ local function addUsableItem( key, id )
class.items = class.items or {}
class.items[ key ] = id
addGearSet( key, id )
addItemSettings( key, id )
end
ns.addUsableItem = addUsableItem ]]
function Hekili:GetAbilityInfo( index )
local ability = class.abilities[ index ]
if not ability then return end
-- Decide if more details are needed later.
return ability.id, ability.name, ability.key, ability.item
end
class.interrupts = {}
local function addPet( key, permanent )
state.pet[ key ] = rawget( state.pet, key ) or {}
state.pet[ key ].name = key
state.pet[ key ].expires = 0
ns.commitKey( key )
end
ns.addPet = addPet
local function addStance( key, spellID )
class.stances[ key ] = spellID
ns.commitKey( key )
end
ns.addStance = addStance
local function setRole( key )
for k,v in pairs( state.role ) do
state.role[ k ] = nil
end
state.role[ key ] = true
end
ns.setRole = setRole
function Hekili:GetActiveSpecOption( opt )
if not self.currentSpecOpts then return end
return self.currentSpecOpts[ opt ]
end
function Hekili:GetActivePack()
return self:GetActiveSpecOption( "package" )
end
Hekili.SpecChangeHistory = {}
function Hekili:SpecializationChanged()
local currentSpec = GetSpecialization()
local currentID = GetSpecializationInfo( currentSpec )
if currentID == nil then
self.PendingSpecializationChange = true
return
end
self.PendingSpecializationChange = false
self:ForceUpdate( "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" )
insert( self.SpecChangeHistory, {
spec = currentID,
time = GetTime(),
bt = debugstack()
} )
for k, _ in pairs( state.spec ) do
state.spec[ k ] = nil
end
for key in pairs( GetResourceInfo() ) do
state[ key ] = nil
class[ key ] = nil
end
class.primaryResource = nil
wipe( state.buff )
wipe( state.debuff )
wipe( class.auras )
wipe( class.abilities )
5 months ago
wipe( class.hooks )
wipe( class.talents )
wipe( class.pvptalents )
wipe( class.powers )
wipe( class.gear )
wipe( class.setBonuses )
wipe( class.packs )
wipe( class.resources )
wipe( class.resourceAuras )
wipe( class.pets )
local specs = {}
-- If the player does not have a specialization, use their first spec instead.
if currentSpec == 5 then
currentSpec = 1
currentID = GetSpecializationInfo( 1 )
end
for i = 1, 4 do
5 months ago
local id, name, _, _, role, primaryStat = GetSpecializationInfo( i )
if not id then break end
if i == currentSpec then
insert( specs, 1, id )
state.spec.id = id
state.spec.name = name
state.spec.key = getSpecializationKey( id )
for k in pairs( state.role ) do
state.role[ k ] = false
end
if role == "DAMAGER" then
state.role.attack = true
elseif role == "TANK" then
state.role.tank = true
else
state.role.healer = true
end
5 months ago
if primaryStat == 1 then
state.spec.primaryStat = "strength"
elseif primaryStat == 2 then
state.spec.primaryStat = "agility"
else
state.spec.primaryStat = "intellect"
end
state.spec[ state.spec.key ] = true
else
insert( specs, id )
end
end
insert( specs, 0 )
for key in pairs( GetResourceInfo() ) do
state[ key ] = nil
class[ key ] = nil
end
if rawget( state, "rune" ) then state.rune = nil; class.rune = nil; end
for k in pairs( class.resourceAuras ) do
class.resourceAuras[ k ] = nil
end
class.primaryResource = nil
for k in pairs( class.stateTables ) do
rawset( state, k, nil )
class.stateTables[ k ] = nil
end
for k in pairs( class.stateFuncs ) do
rawset( state, k, nil )
class.stateFuncs[ k ] = nil
end
for k in pairs( class.stateExprs ) do
class.stateExprs[ k ] = nil
end
self.currentSpec = nil
self.currentSpecOpts = nil
for i, specID in ipairs( specs ) do
local spec = class.specs[ specID ]
if spec then
if specID == currentID then
self.currentSpec = spec
self.currentSpecOpts = rawget( self.DB.profile.specs, specID )
state.settings.spec = self.currentSpecOpts
state.spec.can_dual_cast = spec.can_dual_cast
state.spec.dual_cast = spec.dual_cast
for res, model in pairs( spec.resources ) do
class.resources[ res ] = model
state[ res ] = model.state
end
if rawget( state, "runes" ) then state.rune = state.runes end
for k,v in pairs( spec.resourceAuras ) do
class.resourceAuras[ k ] = v
end
class.primaryResource = spec.primaryResource
for talent, id in pairs( spec.talents ) do
class.talents[ talent ] = id
end
for talent, id in pairs( spec.pvptalents ) do
class.pvptalents[ talent ] = id
end
class.variables = spec.variables
5 months ago
class.potionList.default = "|T967533:0|t |cFFFFD100Default|r"
end
if specID == currentID or specID == 0 then
for event, hooks in pairs( spec.hooks ) do
for _, hook in ipairs( hooks ) do
class.hooks[ event ] = class.hooks[ event ] or {}
insert( class.hooks[ event ], hook )
end
end
end
for res, model in pairs( spec.resources ) do
if not class.resources[ res ] then
class.resources[ res ] = model
state[ res ] = model.state
end
end
5 months ago
if rawget( state, "runes" ) then state.rune = state.runes end
for k, v in pairs( spec.auras ) do
if not class.auras[ k ] then class.auras[ k ] = v end
end
for k, v in pairs( spec.powers ) do
if not class.powers[ k ] then class.powers[ k ] = v end
end
for k, v in pairs( spec.abilities ) do
if not class.abilities[ k ] then class.abilities[ k ] = v end
end
for k, v in pairs( spec.gear ) do
if not class.gear[ k ] then class.gear[ k ] = v end
end
for k, v in pairs( spec.setBonuses ) do
if not class.setBonuses[ k ] then class.setBonuses[ k ] = v end
end
for k, v in pairs( spec.pets ) do
if not class.pets[ k ] then class.pets[ k ] = v end
end
for k, v in pairs( spec.totems ) do
if not class.totems[ k ] then class.totems[ k ] = v end
end
for k, v in pairs( spec.packs ) do
if not class.packs[ k ] then class.packs[ k ] = v end
end
for name, func in pairs( spec.stateExprs ) do
if not class.stateExprs[ name ] then
if rawget( state, name ) then state[ name ] = nil end
class.stateExprs[ name ] = func
end
end
for name, func in pairs( spec.stateFuncs ) do
if not class.stateFuncs[ name ] then
if rawget( state, name ) then
Hekili:Error( "Cannot RegisterStateFunc for an existing expression ( " .. spec.name .. " - " .. name .. " )." )
else
class.stateFuncs[ name ] = func
rawset( state, name, func )
-- Hekili:Error( "Not real error, registered " .. name .. " for " .. spec.name .. " (RSF)." )
end
end
end
for name, t in pairs( spec.stateTables ) do
if not class.stateTables[ name ] then
if rawget( state, name ) then
Hekili:Error( "Cannot RegisterStateTable for an existing expression ( " .. spec.name .. " - " .. name .. " )." )
else
class.stateTables[ name ] = t
rawset( state, name, t )
-- Hekili:Error( "Not real error, registered " .. name .. " for " .. spec.name .. " (RST)." )
end
end
end
if spec.id > 0 then
local s = rawget( Hekili.DB.profile.specs, spec.id )
if s then
for k, v in pairs( spec.settings ) do
if s.settings[ v.name ] == nil then s.settings[ v.name ] = v.default end
end
end
end
end
end
for k in pairs( class.abilityList ) do
local ability = class.abilities[ k ]
if ability and ability.id > 0 then
if not ability.texture or not ability.name then
local data = GetSpellInfo( ability.id )
if data and data.name and data.iconID then
ability.name = ability.name or data.name
class.abilityList[ k ] = "|T" .. data.iconID .. ":0|t " .. ability.name
end
else
class.abilityList[ k ] = "|T" .. ability.texture .. ":0|t " .. ability.name
end
end
end
state.GUID = UnitGUID( "player" )
state.player.unit = UnitGUID( "player" )
ns.callHook( "specializationChanged" )
ns.updateTalents()
ResetDisabledGearAndSpells()
state.swings.mh_speed, state.swings.oh_speed = UnitAttackSpeed( "player" )
HekiliEngine.activeThread = nil
self:UpdateDisplayVisibility()
self:UpdateDamageDetectionForCLEU()
end
do
RegisterEvent( "PLAYER_ENTERING_WORLD", function( event, login, reload )
if login or reload then
local currentSpec = GetSpecialization()
local currentID = GetSpecializationInfo( currentSpec )
if currentID ~= state.spec.id then
Hekili:SpecializationChanged()
end
end
end )
local SpellDisableEvents = {
CHALLENGE_MODE_START = 1,
CHALLENGE_MODE_RESET = 1,
CHALLENGE_MODE_COMPLETED = 1,
PLAYER_ALIVE = 1,
ZONE_CHANGED_NEW_AREA = 1,
QUEST_SESSION_CREATED = 1,
QUEST_SESSION_DESTROYED = 1,
QUEST_SESSION_ENABLED_STATE_CHANGED = 1,
QUEST_SESSION_JOINED = 1,
QUEST_SESSION_LEFT = 1
}
local WipeCovenantCache = ns.WipeCovenantCache
local function CheckSpellsAndGear()
WipeCovenantCache()
ResetDisabledGearAndSpells()
ns.updateGear()
end
for k in pairs( SpellDisableEvents ) do
RegisterEvent( k, function( event )
C_Timer.After( 1, CheckSpellsAndGear )
end )
end
end
class.trinkets = {
[0] = { -- for when nothing is equipped.
},
}
setmetatable( class.trinkets, {
__index = function( t, k )
return t[0]
end
} )