diff --git a/Singer/Singer addon for in gearswap used.lua b/Singer/Singer addon for in gearswap used.lua new file mode 100644 index 0000000..c9bb0e1 --- /dev/null +++ b/Singer/Singer addon for in gearswap used.lua @@ -0,0 +1,47 @@ + + + +--[[ for gearswap work singer addon cycle songs playlist faster and easy + +add in function + +function job_setup() + + send_command('lua l Singer')--;sing off;sing active off + + + state.Singer = M{['description']='Singer','seg','Cuijatender','haste4','seg','seg4','shinryu','shinryu4','mboze','mboze2', 'xevioso', 'kalunga', 'ngai','arebati', 'ongo', 'bumba', + 'haste','haste4', 'magic', 'ph','sortie4', 'ody4', 'ody','sortie',} --'aria', + +end + +function job_self_command(commandArgs, eventArgs) + + if commandArgs[1]:lower() == 'singer' then + send_command('@input //sing playlist "'..state.Singer.value..'"') + end + +end + + +for binds add + +function user_job_setup() + + send_command('bind tab gs c cycle singer;gs c singer') + send_command('bind ^tab gs c cycleback singer;gs c singer') + +end + + + + + + + + + + + + +]] diff --git a/Singer/Singer.lua b/Singer/Singer.lua index 88b6ff1..2a80f59 100644 --- a/Singer/Singer.lua +++ b/Singer/Singer.lua @@ -1,7 +1,7 @@ -_addon.author = 'Ivaar' +_addon.author = 'Ivaar', 'PBW' ,'Aragan' --ADD CCSV AUTO BY 'Aragan' _addon.commands = {'Singer','sing'} _addon.name = 'Singer' -_addon.version = '1.20.08.18' +_addon.version = '1.30.20.0' -- last version update by 'Aragan' require('luau') require('pack') @@ -12,21 +12,26 @@ config = require('config') get = require('sing_get') cast = require('sing_cast') song_timers = require('song_timers') +res = require('resources') -- مكتبة الـ abilities الرسمية + default = { interval = 0.1, - delay=4, - marcato='Sentinel\'s Scherzo', + delay=6, + marcato='Honor March', soul_voice=false, clarion=false, + ccsv=false, actions=false, pianissimo=false, - nightingale=true, - troubadour=true, + nightingale=false, + troubadour=false, + nitro=false, debuffing=false, - recast={song={min=20,max=25},buff={min=5,max=10}}, - active=true, - timers=true, + song_debuffs='', + recast={song={min=0,max=0},buff={min=0,max=0}}, + active=false, + timers=false, aoe={['party']=true, ['p1'] = true,['p2'] = true,['p3'] = true,['p4'] = true,['p5'] = true}, min_ws=20, max_ws=99, @@ -35,6 +40,10 @@ default = { settings = config.load(default) + +-- Force refresh state +_force_refresh = false +_force_refresh_index = 1 setting = T{ buffs = T{ haste = L{}, @@ -44,14 +53,18 @@ setting = T{ }, debuffs = L{}, debuffs = L{"Carnage Elegy","Pining Nocturne",}, - dummy = L{"Knight's Minne","Knight's Minne II",}, - songs = L{"Advancing March","Victory March","Blade Madrigal","Sword Madrigal","Valor Minuet V",}, + dummy = L{"Puppet's Operetta","Scop's Operetta","Shining Fantasia",}, + songs = L{"Honor March","Victory March","Valor Minuet V","Valor Minuet IV",}, song = {}, playlist = T{ clear = L{} }, } - +local song_debuffs = { + ['Carnage Elegy'] = true, + ['Pining Nocturne'] = true, + -- ['Foe Requiem VII'] = true, +} local save_file do @@ -110,18 +123,16 @@ function colorize(row, str) return '\\cs(0,255,0)%s\\cr':format(str) end -local buttons = {'active','actions','nightingale','troubadour','pianissimo','debuffing','party','p1','p2','p3','p4','p5'} - +local buttons = {'active','actions','nitro','ccsv','debuffing','pianissimo','party','p1','p2','p3','p4','p5'} local display_box = function() local str = colorize(1, 'Singer') str = str .. colorize(2, '\n Actions: [%s]':format(settings.actions and 'On' or 'Off')) - + if not settings.active then return str end - - str = str..colorize(3, '\n Nightingale:[%s]':format(settings.nightingale and 'On' or 'Off')) - str = str..colorize(4, '\n Troubadour:[%s]':format(settings.troubadour and 'On' or 'Off')) - str = str..colorize(5, '\n Pianissimo:[%s]':format(settings.pianissimo and 'On' or 'Off')) - str = str..colorize(6, '\n Debuffing:[%s]':format(settings.debuffing and 'On' or 'Off')) + str = str..colorize(3, '\n Nitro:[%s]':format(settings.nitro and 'On' or 'Off')) + str = str..colorize(4, '\n Ccsv:[%s]':format(settings.ccsv and 'On' or 'Off')) + str = str..colorize(5, '\n Debuffing:[%s]':format(settings.debuffing and 'On' or 'Off')) + str = str..colorize(6, '\n Pianissimo:[%s]':format(settings.pianissimo and 'On' or 'Off')) str = str..colorize(7, '\n AoE: [%s]':format(settings.aoe.party and 'On' or 'Off')) if settings.aoe.party then @@ -189,7 +200,7 @@ function do_stuff() if is_moving or buffs.stun or buffs.sleep or buffs.charm or buffs.terror or buffs.petrification then return end local JA_WS_lock = buffs.amnesia or buffs.impairment - + if use_ws and not JA_WS_lock and play.status == 1 then local targ = windower.ffxi.get_mob_by_target('t') local goal_tp @@ -250,19 +261,24 @@ function do_stuff() if settings.debuffing then local targ = windower.ffxi.get_mob_by_target('bt') - + if targ and targ.hpp > 0 and targ.valid_target and targ.distance:sqrt() < 20 then for song in setting.debuffs:it() do local effect - for k,v in pairs(get.debuffs) do + for k, v in pairs(get.debuffs) do if table.find(v, song) then - effect = k + effect = k break end end - - if effect and (not debuffed[targ.id] or not debuffed[targ.id][effect]) and spell_recasts[get.song_by_name(song).id] == 0 then - cast.MA(song,'') + + -- التحقق مما إذا كان الهدف قد تأثر بالفعل بالتعويذة + if effect and (not debuffed[targ.id] or not debuffed[targ.id][effect]) then + if spell_recasts[get.song_by_name(song).id] == 0 then + cast.MA(song, '') + debuffed[targ.id] = debuffed[targ.id] or {} + debuffed[targ.id][effect] = true + end break end end @@ -408,6 +424,10 @@ short_commands = { ['p'] = 'pianissimo', ['n'] = 'nightingale', ['t'] = 'troubadour', + ['n'] = 'nitro', + ['cc'] = 'CCSV', + ['c'] = 'clarion', + ['s'] = 'soul_voice', ['play'] = 'playlist', } @@ -434,7 +454,9 @@ function resolve_song(commands) end windower.register_event('addon command', function(...) - local commands = T(arg):map(windower.convert_auto_trans .. string.lower) + + -- hook for action on/off +local commands = T(arg):map(windower.convert_auto_trans .. string.lower) commands[1] = short_commands[commands[1]] or commands[1] @@ -734,6 +756,29 @@ windower.register_event('addon command', function(...) assert(loadstring(table.concat(commands, ' ',2)))() end bard_status:text(display_box()) + + -- Force Refresh command: //singer fr on|off|toggle|once + if cmd == 'fr' or cmd == 'forcer' or cmd == 'refresh' then + local arg = (args and args[1]) and tostring(args[1]):lower() or 'once' + if arg == 'on' then + _force_refresh = true and true or true + _force_refresh_index = 1 + windower.add_to_chat(207, '[Singer] ForceRefresh: On (queued).') + elseif arg == 'off' then + _force_refresh = false + _force_refresh_index = 1 + windower.add_to_chat(207, '[Singer] ForceRefresh: Off.') + elseif arg == 'toggle' then + _force_refresh = not _force_refresh + if _force_refresh then _force_refresh_index = 1 end + windower.add_to_chat(207, ('[Singer] ForceRefresh: %s.'):format(_force_refresh and 'On' or 'Off')) + else -- once + _force_refresh = true + _force_refresh_index = 1 + windower.add_to_chat(207, '[Singer] ForceRefresh: Once queued.') + end + return + end end) function event_change() @@ -789,3 +834,26 @@ windower.register_event('mouse', mouse_event) windower.register_event('unload', song_timers.reset) windower.register_event('status change', status_change) windower.register_event('zone change','job change','logout', event_change) + + +-- Minimal addon command handler (append) for action on/off if not present +windower.register_event('addon command', function(cmd, ...) + cmd = (cmd or ''):lower() + local args = {...} + if cmd == 'action' or cmd == 'actions' then + local arg = (args[0] or args[1] or ''):lower() + if arg == 'on' then + settings.actions = true + config.save(settings) + _force_refresh = true + _force_refresh_index = 1 + windower.add_to_chat(207, '[Singer] Actions ON → Force refresh queued.') + return + elseif arg == 'off' then + settings.actions = false + config.save(settings) + windower.add_to_chat(207, '[Singer] Actions OFF') + return + end + end +end) diff --git a/Singer/sing_cast.lua b/Singer/sing_cast.lua index 12383e1..621be79 100644 --- a/Singer/sing_cast.lua +++ b/Singer/sing_cast.lua @@ -11,6 +11,26 @@ function cast.MA(str,ta) end function cast.check_song(song_list,targ,buffs,spell_recasts,recasts,JA_WS_lock,recast) + + -- Force refresh AoE songs once when requested + if _force_refresh and (targ == 'AoE' or targ == 'party' or (type(targ) == 'string' and targ:lower():find('aoe')) or type(targ) == 'table') then + _force_refresh_index = _force_refresh_index or 1 + local desired = math.min(#song_list, get.maxsongs(targ, buffs) or #song_list) + local name = song_list[_force_refresh_index] + local song = get.song_by_name(name) + if song and spell_recasts[song.id] == 0 then + cast.MA(song.enl, '') + _force_refresh_index = _force_refresh_index + 1 + if _force_refresh_index > desired then + _force_refresh = false + _force_refresh_index = 1 + windower.add_to_chat(207, '[Singer] Force refresh complete.') + end + return true + else + -- If spell not ready, don't block normal logic; next tick will try again. + end + end local maxsongs = get.maxsongs(targ,buffs) local currsongs = timers[targ] and table.length(timers[targ]) or 0 local basesongs = get.base_songs @@ -35,10 +55,19 @@ function cast.check_song(song_list,targ,buffs,spell_recasts,recasts,JA_WS_lock,r os.time() - timers[targ][song.enl].ts + recast > 0 or (buffs.troubadour and not timers[targ][song.enl].nt) or (buffs['soul voice'] and not timers[targ][song.enl].sv)) then - - if ta == '' and settings.nightingale and not JA_WS_lock and not buffs.nightingale and recasts[109] <= 0 and recasts[110] <= 0 then + + if ta == '' and settings.ccsv and not JA_WS_lock and not buffs['Clarion Call'] and recasts[254] <= 0 and recasts[0] <= 0 then + cast.JA('input /ja \"Clarion Call\" ') + elseif ta == '' and settings.ccsv and not JA_WS_lock and not buffs['Soul Voice'] and recasts[0] <= 0 then + for targ, songs in pairs(timers) do + for song in pairs(songs) do + timers[targ][song].sv = false + end + end + cast.JA('input /ja \"Soul Voice \" ') + elseif ta == '' and settings.nitro and not JA_WS_lock and not buffs.nightingale and recasts[109] <= 0 and recasts[110] <= 0 then cast.JA('input /ja "Nightingale" ') - elseif ta == '' and settings.troubadour and not JA_WS_lock and not buffs.troubadour and recasts[110] <= 0 then + elseif ta == '' and settings.nitro and not JA_WS_lock and not buffs.troubadour and recasts[110] <= 0 then for targ, songs in pairs(timers) do for song in pairs(songs) do timers[targ][song].nt = false diff --git a/Singer/sing_get.lua b/Singer/sing_get.lua index 3b8bbee..9dfc731 100644 --- a/Singer/sing_get.lua +++ b/Singer/sing_get.lua @@ -239,7 +239,11 @@ local equippable_bags = { 'Wardrobe', 'Wardrobe2', 'Wardrobe3', - 'Wardrobe4' + 'Wardrobe4', + 'Wardrobe5', + 'Wardrobe6', + 'Wardrobe7', + 'Wardrobe8' } local extra_song_harp = { diff --git a/Singer/sing_ids.lua b/Singer/sing_ids.lua new file mode 100644 index 0000000..9ccfc63 --- /dev/null +++ b/Singer/sing_ids.lua @@ -0,0 +1,141 @@ +local ids = {} + +ids.buffs = L{ + [195] = 'Paeon', + [196] = 'Ballad', + [197] = 'Minne', + [198] = 'Minuet', + [199] = 'Madrigal', + [200] = 'Prelude', + [201] = 'Mambo', + [202] = 'Aubade', + [203] = 'Pastoral', + [205] = 'Fantasia', + [206] = 'Operetta', + [207] = 'Capriccio', + [209] = 'Round', + [210] = 'Gavotte', + [214] = 'March', + [215] = 'Etude', + [216] = 'Carol', + [218] = 'Hymnus', + [219] = 'Mazurka', + [220] = 'Sirvente', + [221] = 'Dirge', + [222] = 'Scherzo', + } + +ids.spells = L{ + [57] = {id=57,enl='Haste',dur=180}, + [109] = {id=109,enl='Refresh',dur=150}, + } + +ids.songs = L{ + [368] = 'Foe Requiem', + [369] = 'Foe Requiem II', + [370] = 'Foe Requiem III', + [371] = 'Foe Requiem IV', + [372] = 'Foe Requiem V', + [373] = 'Foe Requiem VI', + [374] = 'Foe Requiem VII', + [375] = 'Foe Requiem VIII', + [376] = 'Horde Lullaby', + [377] = 'Horde Lullaby II', + [378] = 'Army\'s Paeon', + [379] = 'Army\'s Paeon II', + [380] = 'Army\'s Paeon III', + [381] = 'Army\'s Paeon IV', + [382] = 'Army\'s Paeon V', + [383] = 'Army\'s Paeon VI', + [384] = 'Army\'s Paeon VII', + [385] = 'Army\'s Paeon VIII', + [386] = 'Mage\'s Ballad', + [387] = 'Mage\'s Ballad II', + [388] = 'Mage\'s Ballad III', + [389] = 'Knight\'s Minne', + [390] = 'Knight\'s Minne II', + [391] = 'Knight\'s Minne III', + [392] = 'Knight\'s Minne IV', + [393] = 'Knight\'s Minne V', + [394] = 'Valor Minuet', + [395] = 'Valor Minuet II', + [396] = 'Valor Minuet III', + [397] = 'Valor Minuet IV', + [398] = 'Valor Minuet V', + [399] = 'Sword Madrigal', + [400] = 'Blade Madrigal', + [401] = 'Hunter\'s Prelude', + [402] = 'Archer\'s Prelude', + [403] = 'Sheepfoe Mambo', + [404] = 'Dragonfoe Mambo', + [405] = 'Fowl Aubade', + [406] = 'Herb Pastoral', + [407] = 'Chocobo Hum', + [408] = 'Shining Fantasia', + [409] = 'Scop\'s Operetta', + [410] = 'Puppet\'s Operetta', + [411] = 'Jester\'s Operetta', + [412] = 'Gold Capriccio', + [413] = 'Devotee Serenade', + [414] = 'Warding Round', + [415] = 'Goblin Gavotte', + [416] = 'Cactuar Fugue', + [417] = 'Honor March', + [418] = 'Protected Aria', + [419] = 'Advancing March', + [420] = 'Victory March', + [421] = 'Battlefield Elegy', + [422] = 'Carnage Elegy', + [423] = 'Massacre Elegy', + [424] = 'Sinewy Etude', + [425] = 'Dextrous Etude', + [426] = 'Vivacious Etude', + [427] = 'Quick Etude', + [428] = 'Learned Etude', + [429] = 'Spirited Etude', + [430] = 'Enchanting Etude', + [431] = 'Herculean Etude', + [432] = 'Uncanny Etude', + [433] = 'Vital Etude', + [434] = 'Swift Etude', + [435] = 'Sage Etude', + [436] = 'Logical Etude', + [437] = 'Bewitching Etude', + [438] = 'Fire Carol', + [439] = 'Ice Carol', + [440] = 'Wind Carol', + [441] = 'Earth Carol', + [442] = 'Lightning Carol', + [443] = 'Water Carol', + [444] = 'Light Carol', + [445] = 'Dark Carol', + [446] = 'Fire Carol II', + [447] = 'Ice Carol II', + [448] = 'Wind Carol II', + [449] = 'Earth Carol II', + [450] = 'Lightning Carol II', + [451] = 'Water Carol II', + [452] = 'Light Carol II', + [453] = 'Dark Carol II', + [454] = 'Fire Threnody', + [455] = 'Ice Threnody', + [456] = 'Wind Threnody', + [457] = 'Earth Threnody', + [458] = 'Ltng. Threnody', + [459] = 'Water Threnody', + [460] = 'Light Threnody', + [461] = 'Dark Threnody', + [462] = 'Magic Finale', + [463] = 'Foe Lullaby', + [464] = 'Goddess\'s Hymnus', + [465] = 'Chocobo Mazurka', + [466] = 'Maiden\'s Virelai', + [467] = 'Raptor Mazurka', + [468] = 'Foe Sirvente', + [469] = 'Adventurer\'s Dirge', + [470] = 'Sentinel\'s Scherzo', + [471] = 'Foe Lullaby II', + [472] = 'Pining Nocturne', + } + +return ids diff --git a/Singer/song_timers.lua b/Singer/song_timers.lua index d16f2de..8f5d1d3 100644 --- a/Singer/song_timers.lua +++ b/Singer/song_timers.lua @@ -15,6 +15,7 @@ song_buffs = { [207] = 'capriccio', [209] = 'round', [210] = 'gavotte', + [213] = 'aira', [214] = 'march', [215] = 'etude', [216] = 'carol', @@ -25,14 +26,8 @@ song_buffs = { [222] = 'scherzo', } -song_debuffs = { - [2] = 'lullaby', - [194] = 'elegy', - [217] = 'threnody', - [223] = 'nocturne', - } - local equip_mods = { + [22249] = {9.0}, -- 'Miracle Cheer', -- 99 [18342] = {0.2}, -- 'Gjallarhorn', -- 75 [18577] = {0.2}, -- 'Gjallarhorn', -- 80 [18578] = {0.2}, -- 'Gjallarhorn', -- 85 @@ -61,6 +56,9 @@ local equip_mods = { [21404] = {0.3}, -- 'Linos' -- assumes +2 songs augment [20629] = {0.05}, -- 'Legato Dagger', [20599] = {0.05}, -- 'Kali', + [22305] = {0.2}, -- Prime Stage 3 + [22306] = {0.2}, -- Prime Stage 4 + [22307] = {0.3}, -- Prime Stage 5 (Final) [27672] = {Paeon=0.1}, -- 'Brioso Roundlet', [27693] = {Paeon=0.1}, -- 'Brioso Roundlet +1', [23049] = {Paeon=0.1}, -- 'Brioso Roundlet +2', @@ -84,14 +82,24 @@ local equip_mods = { [26033] = {0.3}, -- 'Mnbw. Whistle +1', [26758] = {Madrigal=0.1}, -- 'Fili Calot', [26759] = {Madrigal=0.1}, -- 'Fili Calot +1', + [23094] = {Madrigal=0.1}, -- 'Fili Calot +2', + [23429] = {Madrigal=0.1}, -- 'Fili Calot +3', [26916] = {0.11,Minuet=0.1},-- 'Fili Hongreline', [26917] = {0.12,Minuet=0.1},-- 'Fili Hongreline +1', + [23161] = {0.13,Minuet=0.1},-- 'Fili Hongreline +2', + [23496] = {0.14,Minuet=0.1},-- 'Fili Hongreline +3', [27070] = {March=0.1}, -- 'Fili Manchettes', [27071] = {March=0.1}, -- 'Fili Manchettes +1', + [23228] = {March=0.1}, -- 'Fili Manchettes +2', + [23563] = {March=0.1}, -- 'Fili Manchettes +3', [27255] = {Ballad=0.1}, -- 'Fili Rhingrave', [27256] = {Ballad=0.1}, -- 'Fili Rhingrave +1', + [23295] = {Ballad=0.1}, -- 'Fili Rhingrave +2', + [23630] = {Ballad=0.1}, -- 'Fili Rhingrave +3', [27429] = {Scherzo=0.1}, -- 'Fili Cothurnes', [27430] = {Scherzo=0.1}, -- 'Fili Cothurnes +1', + [23362] = {Scherzo=0.1}, -- 'Fili Cothurnes +2', + [23697] = {Scherzo=0.1}, -- 'Fili Cothurnes +3', [26255] = {Madrigal=0.1,Prelude=0.1}, -- 'Intarabus\'s Cape', [25561] = {Etude=0.1}, -- 'Mousai Turban', [25562] = {Etude=0.2}, -- 'Mousai Turban +1', @@ -139,11 +147,7 @@ function song_timers.duration(song_name, buffs) end end end - --[[ - if buff_name then - song_multipliers[buff_name] = mult - end - ]] + return song_timers.calc_dur(song_name, buffs, mult) end @@ -152,12 +156,12 @@ function song_timers.buff_lost(targ_id,buff_id) if buff then local targ = windower.ffxi.get_mob_by_id(targ_id) - if not targ then return end - if not timers[targ] then return end + if not targ.name then return end + if not timers[targ.name] then return end local minimum,song for k,song_name in pairs(buff) do - local song_timer = timers[targ][song_name] + local song_timer = timers[targ.name][song_name] if song_timer and (not minimum or song_timer.ts < minimum) then minimum = song_timer.ts song = song_name @@ -165,16 +169,21 @@ function song_timers.buff_lost(targ_id,buff_id) end if not song then return end - if not settings.song[targ] then song_timers.delete(song,'AoE') end - song_timers.delete(song,targ) + if settings.aoe.party then + local party = windower.ffxi.get_party() + + for slot in get.party_slots:it() do + if settings.aoe[slot] and party[slot].name == targ.name then + song_timers.delete(song,'AoE') + end + end + end + if targ.name == windower.ffxi.get_player().name then + song_timers.delete(song,'AoE') + end + song_timers.delete(song,targ.name) return end - - local debuff = song_debuffs[buff_id] - - if debuff and debuffed[targ_id] then - debuffed[targ_id][debuff] = nil - end end function song_timers.update(targ) @@ -193,14 +202,19 @@ function song_timers.update(targ) end function song_timers.delete(song,targ) - timers[targ][song] = nil - windower.send_command('timers delete "%s [%s]"':format(song,targ)) + if timers[targ] and timers[targ][song] then + timers[targ][song] = nil + end end function song_timers.create(song,targ,dur,current_time,buffs) timers[targ][song] = {ts=current_time+dur,nt=buffs.troubadour,sv=buffs['soul voice']} - if timers.AoE[song] and targ ~= 'AoE' or not settings.timers then return end - windower.send_command('timers create "%s [%s]" %s down':format(song,targ,dur)) + if not settings.aoe.party then + if timers['AoE'] and timers['AoE'][song] then + timers['AoE'][song] = {ts=current_time+dur,nt=buffs.troubadour,sv=buffs['soul voice']} + end + end + if timers['AoE'] and timers['AoE'][song] and targ ~= 'AoE' or not settings.timers then return end end function song_timers.adjust(spell_name,targ,buffs) @@ -211,7 +225,7 @@ function song_timers.adjust(spell_name,targ,buffs) if timers[targ][spell_name].ts < (current_time + dur) then song_timers.create(spell_name,targ,dur,current_time,buffs) end - elseif table.length(timers[targ]) < get.maxsongs(targ,buffs) then + elseif table.length(timers[targ]) < get.maxsongs(targ,buffs) and not check_dummy(targ) then song_timers.create(spell_name,targ,dur,current_time,buffs) else local rep,repsong @@ -224,18 +238,27 @@ function song_timers.adjust(spell_name,targ,buffs) if repsong then song_timers.delete(repsong,targ) song_timers.create(spell_name,targ,dur,current_time,buffs) + if not settings.aoe.party then + song_timers.delete(repsong,'AoE') + song_timers.create(spell_name,'AoE',dur,current_time,buffs) + end end - end + end +end + +function check_dummy(targ) + local count = false + for k,v in pairs (timers[targ]) do + if setting.dummy[k] then + return true + end + end + return false end function song_timers.reset(bool) - for k,targ in pairs(timers) do - for i,v in pairs(targ) do - windower.send_command('timers delete "%s [%s]"':format(i,k)) - end - end if bool then return end - timers = {AoE={},buffs={Haste={},Refresh={}}} + timers = {AoE={}} casting = false end