· 6 years ago · Jul 12, 2019, 12:54 AM
1-------------------------------------------------------------------------------------------------------------------
2-- General utility functions that can be used by any job files.
3-- Outside the scope of what the main include file deals with.
4-------------------------------------------------------------------------------------------------------------------
5
6-------------------------------------------------------------------------------------------------------------------
7-- Buff utility functions.
8-------------------------------------------------------------------------------------------------------------------
9
10local cancel_spells_to_check = S{'Sneak', 'Stoneskin', 'Spectral Jig', 'Trance', 'Monomi: Ichi', 'Utsusemi: Ichi','Utsusemi: Ni','Diamondhide','Magic Barrier'}
11local cancel_types_to_check = S{'Waltz', 'Samba'}
12
13-- Function to cancel buffs if they'd conflict with using the spell you're attempting.
14-- Requirement: Must have Cancel addon installed and loaded for this to work.
15function cancel_conflicting_buffs(spell, spellMap, eventArgs)
16 if cancel_spells_to_check:contains(spell.english) or cancel_types_to_check:contains(spell.type) then
17 if spell.english == 'Spectral Jig' and buffactive.sneak then
18 cast_delay(0.2)
19 send_command('cancel sneak')
20 tickdelay = (framerate * 1.5)
21 elseif spell.english == 'Sneak' and spell.target.type == 'SELF' and buffactive.sneak then
22 send_command('cancel sneak')
23 elseif spell.english == ('Stoneskin') or spell.english == ('Diamondhide') or spell.english == ('Magic Barrier') then
24 send_command('@wait 1;cancel stoneskin')
25 elseif spell.english:startswith('Monomi') then
26 send_command('@wait 1.5;cancel sneak')
27 elseif spell.english == 'Utsusemi: Ni' and player.main_job == 'NIN' and lastshadow == 'Utsusemi: San' then
28 if buffactive['Copy Image (4+)'] and conserveshadows then
29 add_to_chat(123,'Abort: You have four or more shadows.')
30 eventArgs.cancel = true
31 else
32 send_command('@wait '..utsusemi_ni_cancel_delay..';cancel copy image*')
33 end
34 elseif spell.english == 'Utsusemi: Ichi' and lastshadow ~= 'Utsusemi: Ichi' then
35 if (buffactive['Copy Image (3)'] or buffactive['Copy Image (4+)']) and conserveshadows then
36 add_to_chat(123,'Abort: You have three or more shadows.')
37 eventArgs.cancel = true
38 else
39 send_command('@wait '..utsusemi_cancel_delay..';cancel copy image*')
40 end
41 elseif (spell.english == 'Trance' or spell.type=='Waltz') and buffactive['saber dance'] then
42 cast_delay(0.2)
43 send_command('cancel saber dance')
44 tickdelay = (framerate * 1.5)
45 elseif spell.type=='Samba' and buffactive['fan dance'] then
46 cast_delay(0.2)
47 send_command('cancel fan dance')
48 tickdelay = (framerate * 1.5)
49 end
50 end
51end
52
53-- Function to make auto-translate work in windower.
54-- Usage: windower.add_to_chat(207, 'Test ' .. auto_translate(command))
55
56do
57 local cache = {}
58 auto_translate = function(term)
59 if not cache[term] then
60 local entry = res.auto_translates:with('english', term)
61 cache[term] = entry and 'CH>HC':pack(0xFD, 0x0202, entry.id, 0xFD) or term
62 end
63
64 return cache[term]
65 end
66end
67
68-- Some mythics have special durations for level 1 and 2 aftermaths
69local special_aftermath_mythics = S{'Tizona', 'Kenkonken', 'Murgleis', 'Yagrush', 'Carnwenhan', 'Nirvana', 'Tupsimati', 'Idris'}
70
71-- Call from job_precast() to setup aftermath information for custom timers.
72function custom_aftermath_timers_precast(spell)
73 if spell.type == 'WeaponSkill' then
74 info.aftermath = {}
75
76 local relic_ws = data.weaponskills.relic[player.equipment.main] or data.weaponskills.relic[player.equipment.range]
77 local mythic_ws = data.weaponskills.mythic[player.equipment.main] or data.weaponskills.mythic[player.equipment.range]
78 local empy_ws = data.weaponskills.empyrean[player.equipment.main] or data.weaponskills.empyrean[player.equipment.range]
79
80 if not relic_ws and not mythic_ws and not empy_ws then
81 return
82 end
83
84 info.aftermath.weaponskill = spell.english
85 info.aftermath.duration = 0
86
87 info.aftermath.level = math.floor(player.tp / 1000)
88 if info.aftermath.level == 0 then
89 info.aftermath.level = 1
90 end
91
92 if spell.english == relic_ws then
93 info.aftermath.duration = math.floor(0.2 * player.tp)
94 if info.aftermath.duration < 20 then
95 info.aftermath.duration = 20
96 end
97 elseif spell.english == empy_ws then
98 -- nothing can overwrite lvl 3
99 if buffactive['Aftermath: Lv.3'] then
100 return
101 end
102 -- only lvl 3 can overwrite lvl 2
103 if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then
104 return
105 end
106
107 -- duration is based on aftermath level
108 info.aftermath.duration = 30 * info.aftermath.level
109 elseif spell.english == mythic_ws then
110 -- nothing can overwrite lvl 3
111 if buffactive['Aftermath: Lv.3'] then
112 return
113 end
114 -- only lvl 3 can overwrite lvl 2
115 if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then
116 return
117 end
118
119 -- Assume mythic is lvl 80 or higher, for duration
120
121 if info.aftermath.level == 1 then
122 info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 90
123 elseif info.aftermath.level == 2 then
124 info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 120
125 else
126 info.aftermath.duration = 180
127 end
128 end
129 end
130end
131
132
133-- Call from job_aftercast() to create the custom aftermath timer.
134function custom_aftermath_timers_aftercast(spell)
135 if not spell.interrupted and spell.type == 'WeaponSkill' and
136 info.aftermath and info.aftermath.weaponskill == spell.english and info.aftermath.duration > 0 then
137
138 local aftermath_name = 'Aftermath: Lv.'..tostring(info.aftermath.level)
139 send_command('timers d "Aftermath: Lv.1"')
140 send_command('timers d "Aftermath: Lv.2"')
141 send_command('timers d "Aftermath: Lv.3"')
142 send_command('timers c "'..aftermath_name..'" '..tostring(info.aftermath.duration)..' down abilities/00027.png')
143
144 info.aftermath = {}
145 end
146end
147
148
149-------------------------------------------------------------------------------------------------------------------
150-- Utility functions for changing spells and target types in an automatic manner.
151-------------------------------------------------------------------------------------------------------------------
152
153-- waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Cu==--ring Waltz IV'] = 650, ['Curing Waltz V'] = 800}
154
155-- Utility function for automatically adjusting the waltz spell being used to match HP needs and TP limits.
156-- Handle spell changes before attempting any precast stuff.
157--[[
158function refine_waltz(spell, spellMap, eventArgs)
159 if spell.type ~= 'Waltz' then
160 return
161 elseif player.tp < 150 then
162 add_to_chat(123, 'Abort: Insufficient TP ['..tostring(player.tp)..'] to waltz.')
163 eventArgs.cancel = true
164 return
165 end
166
167 if sets.precast.Waltz and sets.precast.Waltz.legs and (sets.precast.Waltz.legs == "Desultor Tassets" or sets.precast.Waltz.legs == "Blitzer Poleyn" or sets.precast.Waltz.legs == "Tatsumaki Sitagoromo") then
168 waltz_tp_cost = {['Curing Waltz'] = 150, ['Curing Waltz II'] = 300, ['Curing Waltz III'] = 450, ['Curing Waltz IV'] = 600, ['Curing Waltz V'] = 750}
169 else
170 waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Curing Waltz IV'] = 650, ['Curing Waltz V'] = 800}
171 end
172
173 -- Don't modify anything for Healing Waltz or Divine Waltzes
174 if spell.english == "Healing Waltz" or spell.english == "Divine Waltz" or spell.english == "Divine Waltz II" then
175 return
176 end
177
178 local newWaltz = spell.english
179 local waltzID
180
181 local missingHP
182
183 -- If curing ourself, get our exact missing HP
184 if spell.target.type == "SELF" then
185 missingHP = player.max_hp - player.hp
186 -- If curing someone in our alliance, we can estimate their missing HP
187 elseif spell.target.isallymember then
188 local target = find_player_in_alliance(spell.target.name)
189 local est_max_hp = target.hp / (target.hpp/100)
190 missingHP = math.floor(est_max_hp - target.hp)
191
192 if buffactive['Contradance'] then
193 missingHP = missingHP / 2
194 end
195 end
196
197 -- If we have an estimated missing HP value, we can adjust the preferred tier used.
198 if missingHP ~= nil then
199 if player.main_job == 'DNC' then
200 local abil_recasts = windower.ffxi.get_ability_recasts()
201 if missingHP < 40 and spell.target.name == player.name then
202 -- Not worth curing yourself for so little.
203 -- Don't block when curing others to allow for waking them up.
204 add_to_chat(123,'Abort: You have full HP!')
205 eventArgs.cancel = true
206 return
207 elseif missingHP < 200 then
208 newWaltz = 'Curing Waltz'
209 waltzID = 190
210 elseif missingHP < 600 then
211 newWaltz = 'Curing Waltz II'
212 waltzID = 191
213 elseif missingHP < 1100 then
214 newWaltz = 'Curing Waltz III'
215 waltzID = 192
216 elseif state.Contradance.value and abil_recasts[229] == 0 then
217 eventArgs.cancel = true
218 windower.chat.input('/ja "Contradance" <me>')
219 windower.chat.input:schedule(1,'/ja "Curing Waltz IV" '..spell.target.raw..'')
220 return
221 elseif missingHP < 1500 then
222 newWaltz = 'Curing Waltz IV'
223 waltzID = 193
224 else
225 newWaltz = 'Curing Waltz V'
226 waltzID = 311
227 end
228 elseif player.sub_job == 'DNC' then
229 if missingHP < 40 and spell.target.name == player.name then
230 -- Not worth curing yourself for so little.
231 -- Don't block when curing others to allow for waking them up.
232 add_to_chat(123,'Abort: You have full HP!')
233 eventArgs.cancel = true
234 return
235 elseif missingHP < 150 then
236 newWaltz = 'Curing Waltz'
237 waltzID = 190
238 elseif missingHP < 300 then
239 newWaltz = 'Curing Waltz II'
240 waltzID = 191
241 else
242 newWaltz = 'Curing Waltz III'
243 waltzID = 192
244 end
245 else
246 -- Not dnc main or sub; bail out
247 return
248 end
249 end
250
251 local tpCost = waltz_tp_cost[newWaltz]
252
253 local downgrade
254
255 -- Downgrade the spell to what we can afford
256 if player.tp < tpCost and not buffactive.trance then
257 Costs:
258 Curing Waltz: 200 TP
259 Curing Waltz II: 350 TP
260 Curing Waltz III: 500 TP
261 Curing Waltz IV: 650 TP
262 Curing Waltz V: 800 TP
263 Divine Waltz: 400 TP
264 Divine Waltz II: 800 TP
265
266
267 if player.tp < 350 then
268 newWaltz = 'Curing Waltz'
269 elseif player.tp < 500 then
270 newWaltz = 'Curing Waltz II'
271 elseif player.tp < 650 then
272 newWaltz = 'Curing Waltz III'
273 elseif player.tp < 800 then
274 newWaltz = 'Curing Waltz IV'
275 end
276
277 downgrade = 'Insufficient TP ['..tostring(player.tp)..']. Downgrading to '..newWaltz..'.'
278 end
279
280
281 if newWaltz ~= spell.english then
282 send_command('@input /ja "'..newWaltz..'" '..tostring(spell.target.raw))
283 if downgrade then
284 add_to_chat(122, downgrade)
285 end
286 eventArgs.cancel = true
287 return
288 end
289
290 if missingHP and missingHP > 0 then
291 add_to_chat(122,'Trying to cure '..tostring(missingHP)..' HP using '..newWaltz..'.')
292 end
293end
294]]
295
296-- Function to allow for automatic adjustment of the spell target type based on preferences.
297 function auto_change_target(spell, spellMap)
298 -- Don't adjust targetting for explicitly named targets
299 if not spell.target.raw:startswith('<') then
300-- return
301 end
302
303 -- Do not modify target for spells where we get <lastst> or <me>
304 if spell.target.raw == ('<lastst>') or spell.target.raw == ('<me>') then
305 return
306 end
307
308 -- init a new eventArgs with current values
309 local eventArgs = {handled = false, PCTargetMode = state.PCTargetMode.value, SelectNPCTargets = state.SelectNPCTargets.value}
310
311 -- Allow the job to do custom handling, or override the default values.
312 -- They can completely handle it, or set one of the secondary eventArgs vars to selectively
313 -- override the default state vars.
314 if job_auto_change_target then
315 job_auto_change_target(spell, action, spellMap, eventArgs)
316 end
317
318 -- If the job handled it, we're done.
319 if eventArgs.handled then
320 return
321 end
322
323 local pcTargetMode = eventArgs.PCTargetMode
324 local selectNPCTargets = eventArgs.SelectNPCTargets
325
326
327 local validPlayers = S{'Self', 'Player', 'Party', 'Ally', 'NPC'}
328
329 local intersection = spell.targets * validPlayers
330 local canUseOnPlayer = not intersection:empty()
331
332 local newTarget
333
334 -- For spells that we can cast on players:
335 if canUseOnPlayer and pcTargetMode ~= 'default' then
336 -- Do not adjust targetting for player-targettable spells where the target was <t>
337 if spell.target.raw ~= ('<t>') then
338 if pcTargetMode == 'stal' then
339 -- Use <stal> if possible, otherwise fall back to <stpt>.
340 if spell.targets.Ally then
341 newTarget = '<stal>'
342 elseif spell.targets.Party then
343 newTarget = '<stpt>'
344 end
345 elseif pcTargetMode == 'stpt' then
346 -- Even ally-possible spells are limited to the current party.
347 if spell.targets.Ally or spell.targets.Party then
348 newTarget = '<stpt>'
349 end
350 elseif pcTargetMode == 'stpc' then
351 -- If it's anything other than a self-only spell, can change to <stpc>.
352 if spell.targets.Player or spell.targets.Party or spell.targets.Ally or spell.targets.NPC then
353 newTarget = '<stpc>'
354 end
355 end
356 end
357 -- For spells that can be used on enemies:
358 elseif spell.targets and spell.targets.Enemy and selectNPCTargets then
359 -- Note: this means macros should be written for <t>, and it will change to <stnpc>
360 -- if the flag is set. It won't change <stnpc> back to <t>.
361 newTarget = '<stnpc>'
362 end
363
364 -- If a new target was selected and is different from the original, call the change function.
365 if newTarget and newTarget ~= spell.target.raw then
366 change_target(newTarget)
367 end
368end
369
370
371-------------------------------------------------------------------------------------------------------------------
372-- Environment utility functions.
373-------------------------------------------------------------------------------------------------------------------
374
375-- Function to get the current weather intensity: 0 for none, 1 for single weather, 2 for double weather.
376function get_weather_intensity()
377 return gearswap.res.weather[world.weather_id].intensity
378end
379
380
381-- Returns true if you're in a party solely comprised of Trust NPCs.
382-- TODO: Do we need a check to see if we're in a party partly comprised of Trust NPCs?
383function is_trust_party()
384 -- Check if we're solo
385 if party.count == 1 then
386 return false
387 end
388
389 -- If we're in an alliance, can't be a Trust party.
390 if alliance[2].count > 0 or alliance[3].count > 0 then
391 return false
392 end
393
394 -- Check that, for each party position aside from our own, the party
395 -- member has one of the Trust NPC names, and that those party members
396 -- are flagged is_npc.
397 for i = 2,6 do
398 if party[i] then
399 if not npcs.Trust:contains(party[i].name) then
400 return false
401 end
402 if party[i].mob and party[i].mob.is_npc == false then
403 return false
404 end
405 end
406 end
407
408 -- If it didn't fail any of the above checks, return true.
409 return true
410end
411
412
413-- Call these function with a list of equipment slots to check ('head', 'neck', 'body', etc)
414-- Returns true if any of the specified slots are currently encumbered.
415-- Returns false if all specified slots are unencumbered.
416function is_encumbered(...)
417 local check_list = {...}
418 -- Compensate for people passing a table instead of a series of strings.
419 if type(check_list[1]) == 'table' then
420 check_list = check_list[1]
421 end
422 local check_set = S(check_list)
423
424 for slot_id,slot_name in pairs(gearswap.default_slot_map) do
425 if check_set:contains(slot_name) then
426 if gearswap.encumbrance_table[slot_id] then
427 return true
428 end
429 end
430 end
431
432 return false
433end
434
435-------------------------------------------------------------------------------------------------------------------
436-- Elemental gear utility functions.
437-------------------------------------------------------------------------------------------------------------------
438
439-- General handler function to set all the elemental gear for an action.
440function set_elemental_gear(spell)
441 --No longer needed because of Fotia.
442 --set_elemental_gorget_belt(spell)
443 set_elemental_obi_cape_ring(spell)
444 set_elemental_staff(spell)
445end
446
447
448--[[ Set the name field of the predefined gear vars for gorgets and belts, for the specified weaponskill. No longer needed because of Fotia.
449function set_elemental_gorget_belt(spell)
450 if spell.type ~= 'WeaponSkill' then
451 return
452 end
453
454 -- Get the union of all the skillchain elements for the weaponskill
455 local weaponskill_elements = S{}:
456 union(skillchain_elements[spell.skillchain_a]):
457 union(skillchain_elements[spell.skillchain_b]):
458 union(skillchain_elements[spell.skillchain_c])
459
460 gear.ElementalGorget.name = get_elemental_item_name("gorget", weaponskill_elements) or gear.default.weaponskill_neck or ""
461 gear.ElementalBelt.name = get_elemental_item_name("belt", weaponskill_elements) or gear.default.weaponskill_waist or ""
462end
463]]--
464
465-- Function to get an appropriate obi/cape/ring for the current action.
466function set_elemental_obi_cape_ring(spell)
467 if spell.element == 'None' then
468 return
469 end
470
471 if spell.element == world.weather_element or spell.element == world.day_element then
472 gear.ElementalObi.name = "Hachirin-no-Obi"
473 gear.ElementalCape.name = "Twilight Cape"
474 else
475 gear.ElementalObi.name = gear.default.obi_waist
476 gear.ElementalCape.name = gear.default.obi_back
477 end
478
479 if spell.element == world.day_element and spell.english ~= 'Impact' and not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then
480 gear.ElementalRing.name = "Zodiac Ring"
481 else
482 gear.ElementalRing.name = gear.default.obi_ring
483 end
484
485--[[
486 local world_elements = S{world.day_element}
487 if world.weather_element ~= 'None' then
488 world_elements:add(world.weather_element)
489 end
490
491 local obi_name = get_elemental_item_name("obi", S{spell.element}, world_elements)
492 gear.ElementalObi.name = obi_name or gear.default.obi_waist or ""
493
494 if obi_name then
495 if player.inventory['Twilight Cape'] or player.wardrobe['Twilight Cape'] or player.wardrobe2['Twilight Cape'] then
496 gear.ElementalCape.name = "Twilight Cape"
497 end
498 if (player.inventory['Zodiac Ring'] or player.wardrobe['Zodiac Ring'] or player.wardrobe2['Twilight Cape']) and spell.english ~= 'Impact' and
499 not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then
500 gear.ElementalRing.name = "Zodiac Ring"
501 end
502 else
503 gear.ElementalCape.name = gear.default.obi_back
504 gear.ElementalRing.name = gear.default.obi_ring
505 end
506]]-- Old Text
507
508end
509
510
511-- Function to get the appropriate fast cast and/or recast staves for the current spell.
512function set_elemental_staff(spell)
513 if spell.action_type ~= 'Magic' then
514 return
515 end
516
517 gear.FastcastStaff.name = get_elemental_item_name("fastcast_staff", S{spell.element}) or gear.default.fastcast_staff or ""
518 gear.RecastStaff.name = get_elemental_item_name("recast_staff", S{spell.element}) or gear.default.recast_staff or ""
519end
520
521
522-- Gets the name of an elementally-aligned piece of gear within the player's
523-- inventory that matches the conditions set in the parameters.
524--
525-- item_type: Type of item as specified in the elemental_map mappings.
526-- EG: gorget, belt, obi, fastcast_staff, recast_staff
527--
528-- valid_elements: Elements that are valid for the action being taken.
529-- IE: Weaponskill skillchain properties, or spell element.
530--
531-- restricted_to_elements: Secondary elemental restriction that limits
532-- whether the item check can be considered valid.
533-- EG: Day or weather elements that have to match the spell element being queried.
534--
535-- Returns: Nil if no match was found (either due to elemental restrictions,
536-- or the gear isn't in the player inventory), or the name of the piece of
537-- gear that matches the query.
538function get_elemental_item_name(item_type, valid_elements, restricted_to_elements)
539 local potential_elements = restricted_to_elements or elements.list
540 local item_map = elements[item_type:lower()..'_of']
541
542 for element in (potential_elements.it or it)(potential_elements) do
543 if valid_elements:contains(element) and (player.inventory[item_map[element]] or player.wardrobe[item_map[element]] or player.wardrobe2[item_map[element]]) or player.wardrobe3[item_map[element]] or player.wardrobe4[item_map[element]] then
544 return item_map[element]
545 end
546 end
547end
548
549
550-------------------------------------------------------------------------------------------------------------------
551-- Function to easily change to a given macro set or book. Book value is optional.
552-------------------------------------------------------------------------------------------------------------------
553
554function set_macro_page(set,book)
555 if not tonumber(set) then
556 add_to_chat(123,'Error setting macro page: Set is not a valid number ('..tostring(set)..').')
557 return
558 end
559 if set < 1 or set > 10 then
560 add_to_chat(123,'Error setting macro page: Macro set ('..tostring(set)..') must be between 1 and 10.')
561 return
562 end
563
564 if book then
565 if not tonumber(book) then
566 add_to_chat(123,'Error setting macro page: book is not a valid number ('..tostring(book)..').')
567 return
568 end
569 if book < 1 or book > 20 then
570 add_to_chat(123,'Error setting macro page: Macro book ('..tostring(book)..') must be between 1 and 20.')
571 return
572 end
573 send_command('@input /macro book '..tostring(book)..';wait .1;input /macro set '..tostring(set))
574 else
575 send_command('@input /macro set '..tostring(set))
576 end
577end
578
579
580-------------------------------------------------------------------------------------------------------------------
581-- Utility functions for including local user files.
582-------------------------------------------------------------------------------------------------------------------
583
584-- Attempt to load user gear files in place of default gear sets.
585-- Return true if one exists and was loaded.
586function load_sidecar(job)
587 if not job then return false end
588
589 -- filename format example for user-local files: whm_gear.lua, or playername_whm_gear.lua
590 local filenames = {player.name..'_'..job..'_gear.lua', job..'_gear.lua',
591 'gear/'..player.name..'_'..job..'_gear.lua', 'gear/'..job..'_gear.lua',
592 'gear/'..player.name..'_'..job..'.lua', 'gear/'..job..'.lua'}
593 return optional_include(filenames)
594end
595
596-- Attempt to include user-globals. Return true if it exists and was loaded.
597function load_user_globals()
598 local filenames = {player.name..'-globals.lua', 'user-globals.lua'}
599 return optional_include(filenames)
600end
601
602-- Optional version of include(). If file does not exist, does not
603-- attempt to load, and does not throw an error.
604-- filenames takes an array of possible file names to include and checks
605-- each one.
606function optional_include(filenames)
607 for _,v in pairs(filenames) do
608 local path = gearswap.pathsearch({v})
609 if path then
610 include(v)
611 return true
612 end
613 end
614end
615
616-------------------------------------------------------------------------------------------------------------------
617-- Utility functions for vars or other data manipulation.
618-------------------------------------------------------------------------------------------------------------------
619
620-- Attempt to locate a specified name within the current alliance.
621function find_player_in_alliance(name)
622 for party_index,ally_party in ipairs(alliance) do
623 for player_index,_player in ipairs(ally_party) do
624 if _player.name == name then
625 return _player
626 end
627 end
628 end
629end
630
631function number_of_jps(jp_tab)
632 local count = 0
633 for _,v in pairs(jp_tab) do
634 count = count + v*(v+1)
635 end
636 return count/2
637end
638
639function add_table_to_chat(table)
640 for k, v in pairs( table ) do
641 add_to_chat(123,''..k..', '..v)
642 end
643end
644
645function get_spell_table_by_name(spell_name)
646 for k in pairs(res.spells) do
647 if res.spells[k][language] == spell_name then
648 return res.spells[k]
649 end
650 end
651 return false
652end
653
654function silent_can_use(spellid)
655 local available_spells = windower.ffxi.get_spells()
656 local spell_jobs = copy_entry(res.spells[spellid].levels)
657
658 -- Filter for spells that you do not know. Exclude Impact.
659 if not available_spells[spellid] and not (spellid == 503 or spellid == 417) then
660 return false
661 -- Filter for spells that you know, but do not currently have access to
662 elseif (not spell_jobs[player.main_job_id] or not (spell_jobs[player.main_job_id] <= player.main_job_level or
663 (spell_jobs[player.main_job_id] >= 100 and number_of_jps(player.job_points[(res.jobs[player.main_job_id].ens):lower()]) >= spell_jobs[player.main_job_id]) ) ) and
664 (not spell_jobs[player.sub_job_id] or not (spell_jobs[player.sub_job_id] <= player.sub_job_level)) then
665 return false
666 else
667
668 return true
669 end
670end
671
672function can_use(spell)
673 local category = outgoing_action_category_table[unify_prefix[spell.prefix
674 if world.in_mog_house then
675 add_to_chat(123,"Abort: You are currently in a Mog House zone.")
676 return false
677 elseif category == 3 then
678 local available_spells = windower.ffxi.get_spells()
679 local spell_jobs = copy_entry(res.spells[spell.id].levels)
680
681 -- Filter for spells that you do not know. Exclude Impact.
682 if not available_spells[spell.id] and not (spell.id == 503 or spell.id == 417) then
683 add_to_chat(123,"Abort: You haven't learned ["..(res.spells[spell.id][language] or spell.id).."].")
684 return false
685 elseif spell.type == 'Ninjutsu' then
686 if player.main_job_id ~= 13 and player.sub_job_id ~= 13 then
687 add_to_chat(123,"Abort: You don't have access to ["..(spell[language] or spell.id).."].")
688 return false
689 elseif not player.inventory[tool_map[spell.english][language and not (player.main_job_id == 13 and player.inventory[universal_tool_map[spell.english][language) then
690 if player.main_job == 'NIN' and player.satchel[universal_tool_map[spell.english][language] then
691 windower.send_command('get "'..universal_tool_map[spell.english][language]..'" satchel 99')
692 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
693 elseif player.satchel[tool_map[spell.english][language] then
694 windower.send_command('get "'..tool_map[spell.english][language]..'" satchel 99')
695 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
696 elseif player.main_job == 'NIN' and player.inventory[universal_toolbag_map[spell.english][language] then
697 windower.chat.input('/item "'..universal_toolbag_map[spell.english][language]..'" <me>')
698 windower.chat.input:schedule(4,'/ma "'..spell.english..'" '..spell.target.raw..'')
699 elseif player.main_job == 'NIN' and player.satchel[universal_toolbag_map[spell.english][language] then
700 windower.send_command('get "'..universal_toolbag_map[spell.english][language]..'" satchel 1')
701 windower.chat.input:schedule(2,'/item "'..universal_toolbag_map[spell.english][language]..'" <me>')
702 windower.chat.input:schedule(6,'/ma "'..spell.english..'" '..spell.target.raw..'')
703 elseif player.inventory[toolbag_map[spell.english][language] then
704 windower.chat.input('/item "'..toolbag_map[spell.english][language]..'" <me>')
705 windower.chat.input:schedule(4,'/ma "'..spell.english..'" '..spell.target.raw..'')
706 elseif player.satchel[toolbag_map[spell.english][language] then
707 windower.send_command('get "'..toolbag_map[spell.english][language]..'" satchel 1')
708 windower.chat.input:schedule(2,'/item "'..universal_toolbag_map[spell.english][language]..'" <me>')
709 windower.chat.input:schedule(6,'/ma "'..spell.english..'" '..spell.target.raw..'')
710 else
711 add_to_chat(123,"Abort: You don't have the proper ninja tool available.")
712 end
713 return false
714 end
715 -- Filter for spells that you know, but do not currently have access to
716 elseif (not spell_jobs[player.main_job_id] or not (spell_jobs[player.main_job_id] <= player.main_job_level or
717 (spell_jobs[player.main_job_id] >= 100 and number_of_jps(player.job_points[__raw.lower(res.jobs[player.main_job_id].ens)]) >= spell_jobs[player.main_job_id]) ) ) and
718 (not spell_jobs[player.sub_job_id] or not (spell_jobs[player.sub_job_id] <= player.sub_job_level)) then
719 add_to_chat(123,"Abort: You don't have access to ["..(res.spells[spell.id][language] or spell.id).."].")
720 return false
721 -- At this point, we know that it is technically castable by this job combination if the right conditions are met.
722 elseif player.main_job_id == 20 and ((addendum_white[spell.id] and not buffactive[401] and not buffactive[416]) or
723 (addendum_black[spell.id] and not buffactive[402] and not buffactive[416])) and
724 not (spell_jobs[player.sub_job_id] and spell_jobs[player.sub_job_id] <= player.sub_job_level) then
725
726 if addendum_white[spell.id] then
727 if state.AutoArts.value and not buffactive["Addendum: White"] and not silent_check_amnesia() and get_current_strategem_count() > 0 then
728 if buffactive["Light Arts"] then
729 windower.chat.input('/ja "Addendum: White" <me>')
730 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
731 else
732 local abil_recasts = windower.ffxi.get_ability_recasts()
733 if abil_recasts[228] == 0 then
734 windower.chat.input('/ja "Light Arts" <me>')
735 windower.chat.input:schedule(1.5,'/ja "Addendum: White" <me>')
736 windower.chat.input:schedule(3.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
737 end
738 end
739 else
740 add_to_chat(123,"Abort: Addendum: White required for ["..(res.spells[spell.id][language] or spell.id).."].")
741 end
742 end
743 if addendum_black[spell.id] then
744 if state.AutoArts.value and not buffactive["Addendum: Black"] and not silent_check_amnesia() and get_current_strategem_count() > 0 then
745 if buffactive["Dark Arts"] then
746 windower.chat.input('/ja "Addendum: Black" <me>')
747 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
748 else
749 local abil_recasts = windower.ffxi.get_ability_recasts()
750 if abil_recasts[232] == 0 then
751 windower.chat.input('/ja "Dark Arts" <me>')
752 windower.chat.input:schedule(1.5,'/ja "Addendum: Black" <me>')
753 windower.chat.input:schedule(3.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
754 end
755 end
756 else
757 add_to_chat(123,"Abort: Addendum: Black required for ["..(res.spells[spell.id][language] or spell.id).."].")
758 end
759 end
760 return false
761 elseif player.sub_job_id == 20 and ((addendum_white[spell.id] and not buffactive[401] and not buffactive[416]) or
762 (addendum_black[spell.id] and not buffactive[402] and not buffactive[416])) and
763 not (spell_jobs[player.main_job_id] and (spell_jobs[player.main_job_id] <= player.main_job_level or
764 (spell_jobs[player.main_job_id] >= 100 and number_of_jps(player.job_points[__raw.lower(res.jobs[player.main_job_id].ens)]) >= spell_jobs[player.main_job_id]) ) ) then
765
766 if addendum_white[spell.id] then
767 if state.AutoArts.value and not buffactive["Addendum: White"] and not silent_check_amnesia() and get_current_strategem_count() > 0 then
768 if buffactive["Light Arts"] then
769 windower.chat.input('/ja "Addendum: White" <me>')
770 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
771 else
772 local abil_recasts = windower.ffxi.get_ability_recasts()
773 if abil_recasts[228] == 0 then
774 windower.chat.input('/ja "Light Arts" <me>')
775 windower.chat.input:schedule(1.5,'/ja "Addendum: White" <me>')
776 windower.chat.input:schedule(3.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
777 end
778 end
779 else
780 add_to_chat(123,"Abort: Addendum: White required for ["..(res.spells[spell.id][language] or spell.id).."].")
781 end
782 end
783 if addendum_black[spell.id] then
784 if state.AutoArts.value and not buffactive["Addendum: Black"] and not silent_check_amnesia() and get_current_strategem_count() > 0 then
785 if buffactive["Dark Arts"] then
786 windower.chat.input('/ja "Addendum: Black" <me>')
787 windower.chat.input:schedule(1.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
788 else
789 local abil_recasts = windower.ffxi.get_ability_recasts()
790 if abil_recasts[232] == 0 then
791 windower.chat.input('/ja "Dark Arts" <me>')
792 windower.chat.input:schedule(1.5,'/ja "Addendum: Black" <me>')
793 windower.chat.input:schedule(3.5,'/ma "'..spell.english..'" '..spell.target.raw..'')
794 end
795 end
796 else
797 add_to_chat(123,"Abort: Addendum: Black required for ["..(res.spells[spell.id][language] or spell.id).."].")
798 end
799 end
800 return false
801 elseif spell.type == 'BlueMagic' and not ((player.main_job_id == 16 and table.contains(windower.ffxi.get_mjob_data().spells,spell.id))
802 or unbridled_learning_set[spell.english]) and
803 not (player.sub_job_id == 16 and table.contains(windower.ffxi.get_sjob_data().spells,spell.id)) then
804 -- This code isn't hurting anything, but it doesn't need to be here either.
805 add_to_chat(123,"Abort: You haven't set ["..(res.spells[spell.id][language] or spell.id).."].")
806 return false
807 end
808 elseif category == 7 or category == 9 then
809 local available = windower.ffxi.get_abilities()
810 if category == 7 and not S(available.weapon_skills)[spell.id] then
811 add_to_chat(123,"Abort: You don't have access to ["..(res.weapon_skills[spell.id][language] or spell.id).."].")
812 return false
813 elseif category == 9 and not S(available.job_abilities)[spell.id] then
814 add_to_chat(123,"Abort: You don't have access to ["..(res.job_abilities[spell.id][language] or spell.id).."].")
815 return false
816 end
817 elseif category == 25 and (not player.main_job_id == 23 or not windower.ffxi.get_mjob_data().species or
818 not res.monstrosity[windower.ffxi.get_mjob_data().species] or not res.monstrosity[windower.ffxi.get_mjob_data().species].tp_moves[spell.id] or
819 not (res.monstrosity[windower.ffxi.get_mjob_data().species].tp_moves[spell.id] <= player.main_job_level)) then
820 -- Monstrosity filtering
821 add_to_chat(123,"Abort: You don't have access to ["..(res.monster_abilities[spell.id][language] or spell.id).."].")
822 return false
823 end
824
825 return true
826end
827
828-- buff_set is a set of buffs in a library table (any of S{}, T{} or L{}).
829-- This function checks if any of those buffs are present on the player.
830function has_any_buff_of(buff_set)
831 return buff_set:any(
832 -- Returns true if any buff from buff set that is sent to this function returns true:
833 function (b) return buffactive[b] end
834 )
835end
836
837
838-- Invert a table such that the keys are values and the values are keys.
839-- Use this to look up the index value of a given entry.
840function invert_table(t)
841 if t == nil then error('Attempting to invert table, received nil.', 2) end
842
843 local i={}
844 for k,v in pairs(t) do
845 i[v] = k
846 end
847 return i
848end
849
850
851-- Gets sub-tables based on baseSet from the string str that may be in dot form
852-- (eg: baseSet=sets, str='precast.FC', this returns the table sets.precast.FC).
853function get_expanded_set(baseSet, str)
854 local cur = baseSet
855 for i in str:gmatch("[^.]+") do
856 if cur then
857 cur = cur[i]
858 end
859 end
860
861 return cur
862end
863
864function copy_entry(tab)
865 if not tab then return nil end
866 local ret = setmetatable(table.reassign({},tab),getmetatable(tab))
867 return ret
868end
869
870-------------------------------------------------------------------------------------------------------------------
871-- Utility functions data and event tracking.
872-------------------------------------------------------------------------------------------------------------------
873
874-- This is a function that can be attached to a registered event for 'time change'.
875-- It will send a call to the update() function if the time period changes.
876-- It will also call job_time_change when any of the specific time class values have changed.
877-- To activate this in your job lua, add this line to your user_setup function:
878-- windower.register_event('time change', time_change)
879--
880-- Variables it sets: classes.Daytime, and classes.DuskToDawn. They are set to true
881-- if their respective descriptors are true, or false otherwise.
882function time_change(new_time, old_time)
883 local was_daytime = classes.Daytime
884 local was_dusktime = classes.DuskToDawn
885
886 if new_time and (new_time >= 6*60 and new_time < 18*60) then
887 classes.Daytime = true
888 else
889 classes.Daytime = false
890 end
891
892 if new_time and (new_time >= 17*60 or new_time < 7*60) then
893 classes.DuskToDawn = true
894 else
895 classes.DuskToDawn = false
896 end
897
898 if was_daytime ~= classes.Daytime or was_dusktime ~= classes.DuskToDawn then
899 if job_time_change then
900 job_time_change(new_time, old_time)
901 end
902
903 handle_update({'auto'})
904 end
905end
906
907--Selindrile's Functions
908
909function item_available(item)
910 if player.inventory[item] or player.wardrobe[item] or player.wardrobe2[item] or player.wardrobe3[item] or player.wardrobe4[item] then
911 return true
912 else
913 return false
914 end
915end
916
917function check_disable(spell, spellMap, eventArgs)
918
919 if player.hp == 0 then
920 add_to_chat(123,'Abort: You are dead.')
921 eventArgs.cancel = true
922 return true
923 elseif buffactive.terror then
924 add_to_chat(123,'Abort: You are terrorized.')
925 eventArgs.cancel = true
926 return true
927 elseif buffactive.petrification then
928 add_to_chat(123,'Abort: You are petrified.')
929 eventArgs.cancel = true
930 return true
931 elseif buffactive.sleep or buffactive.Lullaby then
932 add_to_chat(123,'Abort: You are asleep.')
933 eventArgs.cancel = true
934 return true
935 elseif buffactive.stun then
936 add_to_chat(123,'Abort: You are stunned.')
937 eventArgs.cancel = true
938 return true
939 else
940 return false
941 end
942
943end
944
945function silent_check_disable()
946
947 if buffactive.terror then
948 return true
949 elseif buffactive.petrification then
950 return true
951 elseif buffactive.sleep or buffactive.Lullaby then
952 return true
953 elseif buffactive.stun then
954 return true
955 else
956 return false
957 end
958
959end
960
961-- Checks doom, returns true if we're going to cancel and use an item.
962function check_doom(spell, spellMap, eventArgs)
963
964 if buffactive.doom and not (spell.english == 'Cursna' or spell.name == 'Hallowed Water' or spell.name == 'Holy Water') then
965 if player.inventory['Hallowed Water'] then
966 send_command('input /item "Hallowed Water" <me>')
967 add_to_chat(123,'Abort: You are doomed, using Hallowed Water instead.')
968 eventArgs.cancel = true
969 return true
970 elseif player.inventory['Holy Water'] or player.satchel['Holy Water'] then
971 send_command('input /item "Holy Water" <me>')
972 add_to_chat(123,'Abort: You are doomed, using Holy Water instead.')
973 eventArgs.cancel = true
974 return true
975 end
976 else
977 return false
978 end
979
980end
981
982function check_midaction(spell, spellMap, eventArgs)
983 local in_action, midaction = midaction()
984
985 if eventArgs then
986 if (in_action and midaction.action_type == 'Magic') then
987 eventArgs.cancel = true
988 return true
989 else
990 return false
991 end
992 else
993 if (in_action and midaction.action_type ~= 'Ranged Attack') or pet_midaction() or gearswap.cued_packet then
994 return true
995 else
996 return false
997 end
998 end
999
1000end
1001
1002function check_amnesia(spell, spellMap, eventArgs)
1003
1004 if spell.type == 'WeaponSkill' or spell.action_type == 'Ability' then
1005
1006 if buffactive.amnesia then
1007 add_to_chat(123,'Abort: You have Amnesia.')
1008 eventArgs.cancel = true
1009 return true
1010 elseif buffactive.impairment then
1011 add_to_chat(123,'Abort: Your abilities are restricted.')
1012 eventArgs.cancel = true
1013 return true
1014 else
1015 return false
1016 end
1017
1018 else
1019 return false
1020 end
1021end
1022
1023function silent_check_amnesia()
1024
1025 if buffactive.amnesia or buffactive.impairment then
1026 return true
1027 else
1028 return false
1029 end
1030
1031end
1032
1033function check_silence(spell, spellMap, eventArgs)
1034
1035 if spell.action_type == 'Magic' then
1036
1037 if buffactive.mute then
1038 add_to_chat(123,'Abort: You are muted.')
1039 eventArgs.cancel = true
1040 return true
1041 elseif buffactive.Omerta then
1042 add_to_chat(123,'Abort: Your magic is restricted.')
1043 eventArgs.cancel = true
1044 return true
1045 elseif buffactive.silence then
1046 if player.inventory['Echo Drops'] or player.satchel['Echo Drops'] then
1047 send_command('input /item "Echo Drops" <me>')
1048 elseif player.inventory["Remedy"] then
1049 send_command('input /item "Remedy" <me>')
1050 else
1051 add_to_chat(123,'Abort: You are silenced.')
1052 end
1053
1054 eventArgs.cancel = true
1055 return true
1056 else
1057 return false
1058 end
1059
1060 else
1061 return false
1062 end
1063end
1064
1065function silent_check_silence()
1066
1067 if buffactive.mute or buffactive.Omerta then
1068 return true
1069
1070 elseif buffactive.silence then
1071 if player.inventory['Echo Drops'] or player.satchel['Echo Drops'] then
1072 send_command('input /item "Echo Drops" <me>')
1073 elseif player.inventory["Remedy"] then
1074 send_command('input /item "Remedy" <me>')
1075 end
1076 return true
1077 else
1078 return false
1079 end
1080end
1081
1082function check_recast(spell, spellMap, eventArgs)
1083 if spell.action_type == 'Ability' and spell.type ~= 'WeaponSkill' then
1084 if spell.recast_id == 231 or spell.recast_id == 255 or spell.recast_id == 102 or spell.recast_id == 195 then return false end
1085 local abil_recasts = windower.ffxi.get_ability_recasts()
1086 if not abil_recasts[spell.recast_id] then
1087 add_to_chat(123,"Abort: You don't have access to ["..spell.english.."].")
1088 eventArgs.cancel = true
1089 return true
1090 elseif abil_recasts[spell.recast_id] > 0 then
1091 if spell.english == "Lunge" and abil_recasts[241] == 0 then
1092 eventArgs.cancel = true
1093 windower.send_command('@input /ja "Swipe" <t>')
1094 return true
1095 else
1096 add_to_chat(123,'Abort: ['..spell.english..'] waiting on recast. ('..seconds_to_clock(abil_recasts[spell.recast_id])..')')
1097 eventArgs.cancel = true
1098 return true
1099 end
1100 else
1101 return false
1102 end
1103 elseif spell.action_type == 'Magic' then
1104 local spell_recasts = windower.ffxi.get_spell_recasts()
1105 if spell_recasts[spell.recast_id] > 0 then
1106 if stepdown(spell, eventArgs) then
1107 return true
1108 else
1109 add_to_chat(123,'Abort: ['..spell.english..'] waiting on recast. ('..seconds_to_clock(spell_recasts[spell.recast_id]/60)..')')
1110 eventArgs.cancel = true
1111 return true
1112 end
1113 else
1114 return false
1115 end
1116 else
1117 return false
1118 end
1119
1120end
1121
1122function check_cost(spell, spellMap, eventArgs)
1123 local spellCost = actual_cost(spell)
1124 if spell.action_type == 'Magic' and player.mp < spellCost then
1125 add_to_chat(123,'Abort: '..spell.english..' costs more MP. ('..player.mp..'/'..spellCost..')')
1126 eventArgs.cancel = true
1127 elseif spell.type:startswith('BloodPact') and not buffactive['Astral Conduit'] and player.mp < spellCost then
1128 add_to_chat(123,'Abort: '..spell.english..' costs more MP. ('..player.mp..'/'..spellCost..')')
1129 eventArgs.cancel = true
1130 else
1131 return false
1132 end
1133end
1134
1135function check_targets(spell, spellMap, eventArgs)
1136 if spell.action_type == 'Magic' then
1137 if spell.english:startswith('Curaga') and not spell.target.in_party then
1138 if (buffactive['light arts'] or buffactive['addendum: white']) then
1139 if get_current_strategem_count() > 0 then
1140 local number = spell.name:match('Curaga ?%a*'):sub(7) or ''
1141 eventArgs.cancel = true
1142 if buffactive['Accession'] then
1143 windower.chat.input('/ma "Cure'..number..'" '..spell.target.name..'')
1144 else
1145 windower.chat.input('/ja "Accession" <me>')
1146 windower.chat.input:schedule(1,'/ma "Cure'..number..'" '..spell.target.name..'')
1147 end
1148 else
1149 windower.add_to_chat(123,"Error: Not enough Stratagems to convert Curaga to Accesion Cure.")
1150 end
1151 else
1152 windower.add_to_chat(123,"Error: You can't Curaga outside of party.")
1153 end
1154 return true
1155 end
1156 end
1157
1158 return false
1159end
1160
1161function check_abilities(spell, spellMap, eventArgs)
1162
1163 if spell.action_type == 'Ability' then
1164 if spell.english == "Light Arts" and buffactive['Light Arts'] then
1165 eventArgs.cancel = true
1166 windower.chat.input('/ja "Addendum: White" <me>')
1167 return true
1168 elseif spell.english == "Dark Arts" and buffactive['Dark Arts'] then
1169 eventArgs.cancel = true
1170 windower.chat.input('/ja "Addendum: Black" <me>')
1171 return true
1172 elseif spell.english == "Penury" and buffactive['Dark Arts'] then
1173 eventArgs.cancel = true
1174 windower.chat.input('/ja "Parsimony" <me>')
1175 return true
1176 elseif spell.english == "Parsimony" and buffactive['Light Arts'] then
1177 eventArgs.cancel = true
1178 windower.chat.input('/ja "Penury" <me>')
1179 return true
1180 elseif spell.english == "Celerity" and buffactive['Dark Arts'] then
1181 eventArgs.cancel = true
1182 windower.chat.input('/ja "Alacrity" <me>')
1183 return true
1184 elseif spell.english == "Alacrity" and buffactive['Light Arts'] then
1185 eventArgs.cancel = true
1186 windower.chat.input('/ja "Celerity" <me>')
1187 return true
1188 elseif spell.english == "Accession" and buffactive['Dark Arts'] then
1189 eventArgs.cancel = true
1190 windower.chat.input('/ja "Manifestation" <me>')
1191 return true
1192 elseif spell.english == "Rapture" and buffactive['Dark Arts'] then
1193 eventArgs.cancel = true
1194 windower.chat.input('/ja "Ebullience" <me>')
1195 return true
1196 elseif spell.english == "Ebullience" and buffactive['Light Arts'] then
1197 eventArgs.cancel = true
1198 windower.chat.input('/ja "Rapture" <me>')
1199 return true
1200 elseif spell.english == "Manifestation" and buffactive['Light Arts'] then
1201 eventArgs.cancel = true
1202 windower.chat.input('/ja "Accession" <me>')
1203 return true
1204 elseif spell.english == "Altruism" and buffactive['Dark Arts'] then
1205 eventArgs.cancel = true
1206 windower.chat.input('/ja "Focalization" <me>')
1207 return true
1208 elseif spell.english == "Focalization" and buffactive['Light Arts'] then
1209 eventArgs.cancel = true
1210 windower.chat.input('/ja "Altruism" <me>')
1211 return true
1212 elseif spell.english == "Seigan" and buffactive['Seigan'] then
1213 eventArgs.cancel = true
1214 windower.chat.input('/ja "Third Eye" <me>')
1215 return true
1216 end
1217 end
1218
1219 return false
1220end
1221
1222function stepdown(spell, eventArgs)
1223 if spell_stepdown[spell.english] then
1224 eventArgs.cancel = true
1225 windower.chat.input('/ma "'..spell_stepdown[spell.english]..'" '..spell.target.raw..'')
1226 return true
1227 else
1228 return false
1229 end
1230end
1231
1232function actual_cost(spell)
1233 local cost = spell.mp_cost
1234 if buffactive["Manafont"] or buffactive["Manawell"]
1235 then return 0
1236 elseif spell.type=="WhiteMagic" then
1237 if buffactive["Penury"] then
1238 return cost*.5
1239 elseif buffactive["Light Arts"] or buffactive["Addendum: White"] then
1240 return cost*.9
1241 elseif buffactive["Dark Arts"] or buffactive["Addendum: Black"] then
1242 return cost*1.1
1243 end
1244 elseif spell.type=="BlackMagic" then
1245 if buffactive["Parsimony"] then
1246 return cost*.5
1247 elseif buffactive["Dark Arts"] or buffactive["Addendum: Black"] then
1248 return cost*.9
1249 elseif buffactive["Light Arts"] or buffactive["Addendum: White"] then
1250 return cost*1.1
1251 end
1252 end
1253 return cost
1254end
1255
1256function check_nuke()
1257 if state.AutoNukeMode.value and player.target.type == "MONSTER" then
1258 local spell = res.spells:with('name',autonuke)
1259 local spell_recasts = windower.ffxi.get_spell_recasts()
1260 if spell_recasts[spell.id] == 0 then
1261 windower.chat.input('/ma '..autonuke..' <t>')
1262 tickdelay = (framerate * 1.5)
1263 return true
1264 else
1265 return false
1266 end
1267 else
1268 return false
1269 end
1270end
1271
1272function check_samba()
1273 if not buffactive[''..state.AutoSambaMode.value..''] and windower.ffxi.get_ability_recasts()[216] == 0 and state.AutoSambaMode.value ~= 'Off' and player.tp > 400 then
1274 windower.chat.input('/ja "'..state.AutoSambaMode.value..'" <me>')
1275 tickdelay = (framerate * 1.8)
1276 return true
1277 else
1278 return false
1279 end
1280end
1281
1282function check_sub()
1283 if state.AutoSubMode.value and not areas.Cities:contains(world.area) then
1284 if player.mpp < 70 and player.tp > 999 then
1285 local available_ws = S(windower.ffxi.get_abilities().weapon_skills)
1286
1287 if available_ws:contains(190) then
1288 windower.chat.input('/ws Myrkr <me>')
1289 tickdelay = (framerate * 1.5)
1290 return true
1291 elseif available_ws:contains(173) then
1292 windower.chat.input('/ws Dagan <me>')
1293 tickdelay = (framerate * 1.5)
1294 return true
1295 end
1296 end
1297 if (player.main_job == 'SCH' or player.sub_job == 'SCH') and not buffactive['Refresh'] then
1298 local abil_recasts = windower.ffxi.get_ability_recasts()
1299 if (not (buffactive['Sublimation: Activated'] or buffactive['Sublimation: Complete'])) and abil_recasts[234] == 0 then
1300 windower.chat.input('/ja Sublimation <me>')
1301 tickdelay = (framerate * 1.5)
1302 return true
1303 elseif buffactive['Sublimation: Complete'] and player.mpp < 70 and abil_recasts[234] == 0 then
1304 windower.chat.input('/ja Sublimation <me>')
1305 tickdelay = (framerate * 1.5)
1306 return true
1307 else
1308 return false
1309 end
1310 else
1311 return false
1312 end
1313 else
1314 return false
1315 end
1316end
1317
1318function check_cleanup()
1319 if state.AutoCleanupMode.value then
1320 if player.inventory['Bead Pouch'] then
1321 send_command('input /item "Bead Pouch" <me>')
1322 tickdelay = (framerate * 2)
1323 return true
1324 elseif player.inventory['Silt Pouch'] then
1325 send_command('input /item "Silt Pouch" <me>')
1326 tickdelay = (framerate * 2)
1327 return true
1328 end
1329
1330 local items = windower.ffxi.get_items()
1331 local moveditem = false
1332 if items.count_sack < items.max_sack then
1333 if player.inventory['Pellucid Stone'] then send_command('put "Pellucid Stone" sack all') moveditem = true end
1334 if player.inventory['Taupe Stone'] then send_command('put "Taupe Stone" sack all') moveditem = true end
1335 if player.inventory['Fern Stone'] then send_command('put "Fern Stone" sack all') moveditem = true end
1336 if player.inventory['Frayed Sack (Pel)'] then send_command('put "Frayed Sack (Pel)" sack all') moveditem = true end
1337 if player.inventory['Frayed Sack (Tau)'] then send_command('put "Frayed Sack (Tau)" sack all') moveditem = true end
1338 if player.inventory['Frayed Sack (Fer)'] then send_command('put "Frayed Sack (Fer)" sack all') moveditem = true end
1339 if player.inventory['Beitetsu'] then send_command('put Beitetsu sack all') moveditem = true end
1340 if player.inventory['Beitetsu Parcel'] then send_command('put "Beitetsu Parcel" sack all') moveditem = true end
1341 if player.inventory['Beitetsu Box'] then send_command('put "Beitetsu Box" sack all') moveditem = true end
1342 if player.inventory['Pluton'] then send_command('put Pluton sack all') moveditem = true end
1343 if player.inventory['Pluton Case'] then send_command('put "Pluton Case" sack all') moveditem = true end
1344 if player.inventory['Pluton Box'] then send_command('put "Pluton Box" sack all') moveditem = true end
1345 if player.inventory['Riftborn Boulder'] then send_command('put "Riftborn Boulder" sack all') moveditem = true end
1346 if player.inventory['Boulder Case'] then send_command('put "Boulder Case" sack all') moveditem = true end
1347 if player.inventory['Boulder Box'] then send_command('put "Boulder Box" sack all') moveditem = true end
1348 end
1349
1350 if not state.Capacity.value then
1351 if player.inventory['Mecisto. Mantle'] then send_command('put "Mecisto. Mantle" satchel') moveditem = true end
1352 if player.inventory['Trizek Ring'] then send_command('put "Trizek Ring" satchel') moveditem = true end
1353 if player.inventory['Capacity Ring'] then send_command('put "Capacity Ring" satchel') moveditem = true end
1354 if player.inventory['Vocation Ring'] then send_command('put "Vocation Ring" satchel') moveditem = true end
1355 if player.inventory['Facility Ring'] then send_command('put "Facility Ring" satchel') moveditem = true end
1356 if player.inventory['Guide Beret'] then send_command('put "Guide Beret" satchel') moveditem = true end
1357 end
1358
1359 if moveditem then tickdelay = (framerate * 2.3) return true end
1360
1361 local shard_name = {'C. Ygg. Shard ','Z. Ygg. Shard ','A. Ygg. Shard ','P. Ygg. Shard '}
1362
1363 for sni, snv in ipairs(shard_name) do
1364 local shard_count = {'I','II','III','IV','V'}
1365 for sci, scv in ipairs(shard_count) do
1366 if player.inventory[snv..''..scv] then
1367 send_command('wait 3.0;input /item "'..snv..''..scv..'" <me>')
1368 tickdelay = (framerate * 2)
1369 return true
1370 end
1371 end
1372 end
1373
1374 return false
1375 else
1376 return false
1377 end
1378end
1379
1380function check_trust()
1381 if not moving and state.AutoTrustMode.value and not areas.Cities:contains(world.area) and (buffactive['Reive Mark'] or buffactive['Elvorseal'] or not player.in_combat) then
1382 local party = windower.ffxi.get_party()
1383 if party.p5 == nil then
1384 local spell_recasts = windower.ffxi.get_spell_recasts()
1385
1386 if spell_recasts[979] == 0 and not have_trust("Selh'teus") then
1387 windower.chat.input('/ma "Selh\'teus" <me>')
1388 tickdelay = (framerate * 4.5)
1389 return true
1390 elseif spell_recasts[1012] == 0 and not have_trust("Nashmeira") then
1391 windower.chat.input('/ma "Nashmeira II" <me>')
1392 tickdelay = (framerate * 4.5)
1393 return true
1394 elseif spell_recasts[1018] == 0 and not have_trust("Iroha") then
1395 windower.chat.input('/ma "Iroha II" <me>')
1396 tickdelay = (framerate * 4.5)
1397 return true
1398 elseif spell_recasts[1017] == 0 and not have_trust("Arciela") then
1399 windower.chat.input('/ma "Arciela II" <me>')
1400 tickdelay = (framerate * 4.5)
1401 return true
1402 elseif spell_recasts[947] == 0 and not have_trust("UkaTotlihn") then
1403 windower.chat.input('/ma "Uka Totlihn" <me>')
1404 tickdelay = (framerate * 4.5)
1405 return true
1406 elseif spell_recasts[1013] == 0 and not have_trust("Lilisette") then
1407 windower.chat.input('/ma "Lilisette II" <me>')
1408 tickdelay = (framerate * 4.5)
1409 return true
1410 else
1411 return false
1412 end
1413 end
1414
1415 end
1416 return false
1417end
1418
1419function check_auto_tank_ws()
1420 if state.AutoWSMode.value and state.AutoTankMode.value and player.target.type == "MONSTER" and not moving and player.status == 'Engaged' and not silent_check_amnesia() then
1421 if player.tp > 999 and relic_weapons:contains(player.equipment.main) and state.RelicAftermath and (not buffactive['Aftermath']) then
1422 windower.chat.input('/ws "'..data.weaponskills.relic[player.equipment.main]..'" <t>')
1423 tickdelay = (framerate * 1.8)
1424 return true
1425 elseif player.tp > 999 and (buffactive['Aftermath: Lv.3'] or not mythic_weapons:contains(player.equipment.main)) then
1426 windower.chat.input('/ws "'..autows..'" <t>')
1427 tickdelay = (framerate * 1.8)
1428 return true
1429 elseif player.tp == 3000 then
1430 windower.chat.input('/ws "'..data.weaponskills.mythic[player.equipment.main]..'" <t>')
1431 tickdelay = (framerate * 1.8)
1432 return true
1433 else
1434 return false
1435 end
1436 end
1437end
1438
1439function check_use_item()
1440 if useItem then
1441 local CurrentTime = (os.time(os.date('!*t')) + time_offset)
1442 if useItemSlot == 'item' and player.inventory[useItemName] then
1443 windower.chat.input('/item "'..useItemName..'" <me>')
1444 tickdelay = (framerate * 2)
1445 return true
1446 elseif useItemSlot == 'set' then
1447 if item_equipped(set_to_item(useItemName)) and get_item_next_use(set_to_item(useItemName)).usable then
1448 windower.chat.input('/item "'..set_to_item(useItemName)..'" <me>')
1449 tickdelay = (framerate * 3)
1450 return true
1451 elseif item_available(set_to_item(useItemName)) and ((get_item_next_use(set_to_item(useItemName)).next_use_time) - CurrentTime) < 10 then
1452 windower.send_command('gs c forceequip '..useItemSlot..' '..useItemName..'')
1453 tickdelay = (framerate * 2)
1454 return true
1455 elseif player.satchel[set_to_item(useItemName)] then
1456 windower.send_command('get "'..set_to_item(useItemName)..'" satchel')
1457 tickdelay = (framerate * 2)
1458 return true
1459 else
1460 add_to_chat(123,''..set_to_item(useItemName)..' not available or ready for use.')
1461 useItem = false
1462 return false
1463 end
1464 elseif item_equipped(useItemName) and get_item_next_use(useItemName).usable then
1465 windower.chat.input('/item "'..useItemName..'" <me>')
1466 tickdelay = (framerate * 3)
1467 return true
1468 elseif item_available(useItemName) and ((get_item_next_use(useItemName).next_use_time) - CurrentTime) < 10 then
1469 windower.send_command('gs c forceequip '..useItemSlot..' '..useItemName..'')
1470 tickdelay = (framerate * 2)
1471 return true
1472 elseif player.satchel[useItemName] then
1473 windower.send_command('get '..useItemName..'')
1474 tickdelay = (framerate * 2)
1475 return true
1476 else
1477 add_to_chat(123,''..useItemName..' not available or ready for use.')
1478 useItem = false
1479 return false
1480 end
1481 else
1482 return false
1483 end
1484 return false
1485end
1486
1487function check_food()
1488 if state.AutoFoodMode.value and not buffactive['Food'] and not areas.Cities:contains(world.area) then
1489
1490 if player.inventory[''..autofood..''] then
1491 windower.chat.input('/item "'..autofood..'" <me>')
1492 tickdelay = (framerate * 1.5)
1493 return true
1494 elseif player.satchel[''..autofood..''] then
1495 windower.send_command('get "'..autofood..'" satchel')
1496 tickdelay = (framerate * 1.5)
1497 return true
1498 else
1499 return false
1500 end
1501
1502 else
1503 return false
1504 end
1505end
1506
1507function check_ws()
1508 if state.AutoWSMode.value and player.status == 'Engaged' and player.target.type == "MONSTER" and player.tp > 999 and not silent_check_amnesia() and player.target and not (player.target.distance > (19.7 + player.target.model_size)) then
1509
1510 local available_ws = S(windower.ffxi.get_abilities().weapon_skills)
1511
1512 if player.hpp < 41 and available_ws:contains(47) and player.target.distance < (3.2 + player.target.model_size) then
1513 windower.chat.input('/ws "Sanguine Blade" <t>')
1514 tickdelay = (framerate * 1.8)
1515 return true
1516 elseif player.hpp < 41 and available_ws:contains(105) and player.target.distance < (3.2 + player.target.model_size) then
1517 windower.chat.input('/ws "Catastrophe" <t>')
1518 tickdelay = (framerate * 1.8)
1519 return true
1520 elseif player.mpp < 21 and available_ws:contains(109) and player.target.distance < (3.2 + player.target.model_size) then
1521 windower.chat.input('/ws "Entropy" <t>')
1522 tickdelay = (framerate * 1.8)
1523 return true
1524 elseif player.mpp < 21 and available_ws:contains(171) and player.target.distance < (3.2 + player.target.model_size) then
1525 windower.chat.input('/ws "Mystic Boon" <t>')
1526 tickdelay = (framerate * 1.8)
1527 return true
1528 elseif player.target.distance > (3.2 + player.target.model_size) and not data.weaponskills.ranged:contains(autows) then
1529 return false
1530 elseif player.tp > 999 and relic_weapons:contains(player.equipment.main) and state.RelicAftermath and (not buffactive['Aftermath']) then
1531 windower.chat.input('/ws "'..data.weaponskills.relic[player.equipment.main]..'" <t>')
1532 tickdelay = (framerate * 1.8)
1533 return true
1534 elseif (buffactive['Aftermath: Lv.3'] or not mythic_weapons:contains(player.equipment.main)) and player.tp >= autowstp then
1535 windower.chat.input('/ws "'..autows..'" <t>')
1536 tickdelay = (framerate * 1.8)
1537 return true
1538 elseif player.tp == 3000 then
1539 windower.chat.input('/ws "'..data.weaponskills.mythic[player.equipment.main]..'" <t>')
1540 tickdelay = (framerate * 1.8)
1541 return true
1542 else
1543 return false
1544 end
1545 else
1546 return false
1547 end
1548end
1549
1550function have_trust(trustname)
1551 local party = windower.ffxi.get_party()
1552
1553 for i = 1,5 do
1554 local member = party['p' .. i]
1555 if member then
1556 if member.name:lower() == trustname:lower() then return true end
1557 end
1558
1559 end
1560
1561 return false
1562end
1563
1564function is_party_member(playerid)
1565 local party = windower.ffxi.get_party()
1566
1567 for i = 1,5 do
1568 local member = party['p' .. i]
1569 if member.mob.id then
1570 if member.mob.id == playerid then return true end
1571 end
1572
1573 end
1574
1575 return false
1576end
1577
1578function get_item_next_use(name)--returns time that you can use the item again
1579 for _,n in pairs({"inventory","wardrobe","wardrobe2","wardrobe3","wardrobe4"}) do
1580 for _,v in pairs(gearswap.items[n]) do
1581 if type(v) == "table" and v.id ~= 0 and res.items[v.id].english:lower() == name:lower() then
1582 return extdata.decode(v)
1583 end
1584 end
1585 end
1586end
1587
1588function cp_ring_equip(ring)--equips given ring
1589 enable("left_ring")
1590 gearswap.equip_sets('equip_command',nil,{left_ring=ring})
1591 disable("left_ring")
1592end
1593
1594function check_cpring()
1595-- local CurrentTime = (os.time(os.date("!*t", os.time())) + time_offset)
1596 local CurrentTime = (os.time(os.date('!*t')) + time_offset)
1597
1598 if player.main_job_level < 99 then
1599 if player.equipment.head and player.equipment.head == 'Sprout Beret' and get_item_next_use(player.equipment.head).usable then
1600 send_command('input /item "'..player.equipment.head..'" <me>')
1601 cp_delay = 0
1602 return true
1603
1604 elseif item_available('Sprout Beret') and ((get_item_next_use('Sprout Beret').next_use_time) - CurrentTime) < 15 and (get_item_next_use('Sprout Beret').charges_remaining > 0) then
1605 enable("head")
1606 gearswap.equip_sets('equip_command',nil,{head="Sprout Beret"})
1607 disable("head")
1608 cp_delay = 10
1609 return true
1610
1611 elseif player.equipment.left_ring == 'Echad Ring' and get_item_next_use('Echad Ring').usable then
1612 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1613 cp_delay = 0
1614 return true
1615 elseif item_available('Echad Ring') and ((get_item_next_use('Echad Ring').next_use_time) - CurrentTime) < 15 then
1616 cp_ring_equip('Echad Ring')
1617 cp_delay = 10
1618 return true
1619
1620 elseif player.equipment.left_ring == 'Caliber Ring' and get_item_next_use('Caliber Ring').usable then
1621 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1622 cp_delay = 0
1623 return true
1624 elseif item_available('Caliber Ring') and ((get_item_next_use('Caliber Ring').next_use_time) - CurrentTime) < 15 then
1625 cp_ring_equip('Caliber Ring')
1626 cp_delay = 10
1627 return true
1628
1629 elseif player.equipment.left_ring == 'Emperor Band' and get_item_next_use('Emperor Band').usable then
1630 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1631 cp_delay = 0
1632 return true
1633 elseif item_available('Emperor Band') and ((get_item_next_use('Emperor Band').next_use_time) - CurrentTime) < 15 then
1634 cp_ring_equip('Emperor Band')
1635 cp_delay = 10
1636 return true
1637
1638 elseif player.equipment.left_ring == 'Empress Band' and get_item_next_use('Empress Band').usable then
1639 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1640 cp_delay = 0
1641 return true
1642 elseif item_available('Empress Band') and ((get_item_next_use('Empress Band').next_use_time) - CurrentTime) < 15 then
1643 cp_ring_equip('Empress Band')
1644 cp_delay = 10
1645 return true
1646
1647 elseif player.equipment.left_ring == 'Resolution Ring' and get_item_next_use('Resolution Ring').usable then
1648 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1649 cp_delay = 0
1650 return true
1651 elseif item_available('Resolution Ring') and ((get_item_next_use('Resolution Ring').next_use_time) - CurrentTime) < 15 then
1652 cp_ring_equip('Resolution Ring')
1653 cp_delay = 10
1654 return true
1655
1656 else
1657 cp_delay = 0
1658 return false
1659 end
1660
1661 elseif cprings:contains(player.equipment.left_ring) and get_item_next_use(player.equipment.left_ring).usable then
1662 send_command('input /item "'..player.equipment.left_ring..'" <me>')
1663 cp_delay = 0
1664 return true
1665
1666 elseif player.equipment.head and player.equipment.head == 'Guide Beret' and get_item_next_use(player.equipment.head).usable then
1667 send_command('input /item "'..player.equipment.head..'" <me>')
1668 cp_delay = 0
1669 return true
1670
1671 elseif item_available('Guide Beret') and ((get_item_next_use('Guide Beret').next_use_time) - CurrentTime) < 15 and (get_item_next_use('Guide Beret').charges_remaining > 0) then
1672 enable("head")
1673 gearswap.equip_sets('equip_command',nil,{head="Guide Beret"})
1674 disable("head")
1675 cp_delay = 10
1676 return true
1677
1678 elseif item_available('Trizek Ring') and ((get_item_next_use('Trizek Ring').next_use_time) - CurrentTime) < 15 then
1679 cp_ring_equip('Trizek Ring')
1680 cp_delay = 10
1681 return true
1682
1683 elseif item_available('Capacity Ring') and ((get_item_next_use('Capacity Ring').next_use_time) - CurrentTime) < 15 and (get_item_next_use('Capacity Ring').charges_remaining > 0) then
1684 cp_ring_equip('Capacity Ring')
1685 cp_delay = 10
1686 return true
1687
1688 elseif item_available('Vocation Ring') and ((get_item_next_use('Vocation Ring').next_use_time) - CurrentTime) < 15 and (get_item_next_use('Vocation Ring').charges_remaining > 0) then
1689 cp_ring_equip('Vocation Ring')
1690 cp_delay = 10
1691 return true
1692
1693 elseif item_available('Facility Ring') and ((get_item_next_use('Facility Ring').next_use_time) - CurrentTime) < 15 and (get_item_next_use('Facility Ring').charges_remaining > 0) then
1694 cp_ring_equip('Facility Ring')
1695 cp_delay = 10
1696 return true
1697
1698 elseif player.equipment.head and player.equipment.head == 'Guide Beret' and (((get_item_next_use(player.equipment.head).next_use_time) - CurrentTime) > 15 or (get_item_next_use(player.equipment.head).charges_remaining == 0)) then
1699 enable("head")
1700 handle_equipping_gear(player.status)
1701 cp_delay = 19
1702 return true
1703
1704 elseif cprings:contains(player.equipment.left_ring) and (((get_item_next_use(player.equipment.left_ring).next_use_time) - CurrentTime) > 15 or (get_item_next_use(player.equipment.left_ring).charges_remaining == 0)) then
1705 enable("left_ring")
1706 handle_equipping_gear(player.status)
1707 cp_delay = 19
1708 return true
1709
1710 end
1711
1712 cp_delay = 0
1713 return false
1714end
1715
1716function check_cpring_buff()-- returs true if you do not have the buff from xp cp ring
1717 cp_delay = cp_delay + 1
1718
1719 if time_test then
1720 local CurrentTime = (os.time(os.date("!*t", os.time())) + time_offset)
1721 windower.add_to_chat(123,"Capacity Ring Next Use: "..(get_item_next_use('Capacity Ring').next_use_time - CurrentTime).."")
1722 end
1723
1724 if state.Capacity.value and cp_delay > 20 and not moving and not areas.Cities:contains(world.area) then
1725
1726 if player.satchel['Mecisto. Mantle'] then send_command('get "Mecisto. Mantle" satchel;wait 2;gs c update') end
1727 if player.satchel['Trizek Ring'] then send_command('get "Trizek Ring" satchel') end
1728 if player.satchel['Capacity Ring'] then send_command('get "Capacity Ring" satchel') end
1729 if player.satchel['Vocation Ring'] then send_command('get "Vocation Ring" satchel') end
1730 if player.satchel['Facility Ring'] then send_command('get "Facility Ring" satchel') end
1731 if player.satchel['Guide Beret'] then send_command('get "Guide Beret" satchel') end
1732 if player.satchel['Echad Ring'] and player.main_job_level < 99 then send_command('get "Guide Beret" satchel') end
1733
1734 if buffactive['Commitment'] then
1735 return false
1736 elseif buffactive['Dedication'] == 2 then
1737 return false
1738 elseif not buffactive['Dedication'] then
1739 if check_cpring() then
1740 return true
1741 else
1742 return false
1743 end
1744 elseif buffactive['Dedication'] == 1 then
1745 if have_trust("Kupofried") then
1746 if check_cpring() then
1747 return true
1748 else
1749 return false
1750 end
1751 else
1752 return false
1753 end
1754 end
1755 else
1756 return false
1757 end
1758 return false
1759end
1760
1761function is_defensive()
1762 if state.DefenseMode.value ~= 'None' or state.HybridMode.value:contains('DT') or state.HybridMode.value:contains('Tank') then
1763 return true
1764 else
1765 return false
1766 end
1767end
1768
1769function has_shadows()
1770 if buffactive["Copy Image (4+)"] then
1771 return 4
1772 elseif buffactive["Copy Image (3)"] then
1773 return 3
1774 elseif buffactive["Copy Image (2)"] then
1775 return 2
1776 elseif buffactive.Blink or buffactive["Copy Image"] then
1777 return 1
1778 else
1779 return 0
1780 end
1781end
1782
1783function is_nuke(spell, spellMap)
1784 if (
1785 (spell.skill == 'Elemental Magic' and spellMap ~= 'ElementalEnfeeble') or
1786 (player.main_job == 'BLU' and spell.skill == 'Blue Magic' and spellMap:contains('Magical')) or
1787 (player.main_job == 'NIN' and spell.skill == 'Ninjutsu' and spellMap:contains('ElementalNinjutsu')) or
1788 spell.english == 'Comet' or spell.english == 'Meteor' or spell.english == 'Impact' or spell.english == 'Death' or
1789 spell.english:startswith('Banish')
1790 ) then
1791
1792 return true
1793 else
1794 return false
1795 end
1796end
1797
1798function ammo_left()
1799
1800 local InventoryAmmo = ((player.inventory[player.equipment.ammo] or {}).count or 0)
1801 local WardrobeAmmo = ((player.wardrobe[player.equipment.ammo] or {}).count or 0)
1802 local Wardrobe2Ammo = ((player.wardrobe2[player.equipment.ammo] or {}).count or 0)
1803 local Wardrobe3Ammo = ((player.wardrobe3[player.equipment.ammo] or {}).count or 0)
1804 local Wardrobe4Ammo = ((player.wardrobe4[player.equipment.ammo] or {}).count or 0)
1805
1806 local AmmoLeft = InventoryAmmo + WardrobeAmmo + Wardrobe2Ammo + Wardrobe3Ammo + Wardrobe4Ammo
1807
1808 return AmmoLeft
1809end
1810
1811 --Equip command but accepts the set name as a string to work around inability to use equip() in raw events.
1812function do_equip(setname)
1813 send_command('gs equip '..setname..'')
1814end
1815
1816function seconds_to_clock(seconds)
1817 local seconds = tonumber(seconds)
1818
1819 if seconds <= 0 then
1820 return "00:00:00";
1821 else
1822 hours = string.format("%01.f", math.floor(seconds/3600));
1823 mins = string.format("%02.f", math.floor(seconds/60 - (hours*60)));
1824 secs = string.format("%02.f", math.floor(seconds - hours*3600 - mins *60));
1825 return hours..":"..mins..":"..secs
1826 end
1827end
1828
1829function parse_set_to_keys(str)
1830 if type(str) == 'table' then
1831 str = table.concat(str, ' ')
1832 end
1833
1834 -- Parsing results get pushed into the result list.
1835 local result = L{}
1836
1837 local remainder = str
1838 local key
1839 local stop
1840 local sep = '.'
1841 local count = 0
1842
1843 -- Loop as long as remainder hasn't been nil'd or reduced to 0 characters, but only to a maximum of 30 tries.
1844 while remainder and #remainder and count < 30 do
1845 -- Try aaa.bbb set names first
1846 while sep == '.' do
1847 _,_,key,sep,remainder = remainder:find("^([^%.%[]*)(%.?%[?)(.*)")
1848 -- "key" is everything that is not . or [ 0 or more times.
1849 -- "sep" is the next divider, which is necessarily . or [
1850 -- "remainder" is everything after that
1851 result:append(key)
1852 end
1853
1854 -- Then try aaa['bbb'] set names.
1855 -- Be sure to account for both single and double quote enclosures.
1856 -- Ignore periods contained within quote strings.
1857 while sep == '[' do
1858 _,_,sep,remainder = remainder:find([=[^(%'?%"?)(.*)]=]) --' --block bad text highlighting
1859 -- "sep" is the first ' or " found (or nil)
1860 -- remainder is everything after that (or nil)
1861 if sep == "'" then
1862 _,_,key,stop,sep,remainder = remainder:find("^([^']+)('])(%.?%[?)(.*)")
1863 elseif sep == '"' then
1864 _,_,key,stop,sep,remainder = remainder:find('^([^"]+)("])(%.?%[?)(.*)')
1865 elseif not sep or #sep == 0 then
1866 -- If there is no single or double quote detected, attempt to treat the index as a number or boolean
1867 local _,_,pot_key,pot_stop,pot_sep,pot_remainder = remainder:find('^([^%]]+)(])(%.?%[?)(.*)')
1868 if tonumber(pot_key) then
1869 key,stop,sep,remainder = tonumber(pot_key),pot_stop,pot_sep,pot_remainder
1870 elseif pot_key == 'true' then
1871 key,stop,sep,remainder = true,pot_stop,pot_sep,pot_remainder
1872 elseif pot_key == 'false' then
1873 key,stop,sep,remainder = false,pot_stop,pot_sep,pot_remainder
1874 end
1875 end
1876 result:append(key)
1877 end
1878
1879 count = count +1
1880 end
1881
1882 return result
1883end
1884
1885function get_set_from_keys(keys)
1886 local set = keys[1] == 'sets' and _G or sets
1887 for key in (keys.it or it)(keys) do
1888 if key == nil then
1889 return nil
1890 end
1891 set = set[key]
1892 if not set then
1893 return nil
1894 end
1895 end
1896
1897 return set
1898end
1899
1900function face_target()
1901 local target = windower.ffxi.get_mob_by_index(windower.ffxi.get_player().target_index or 0)
1902 local self_vector = windower.ffxi.get_mob_by_index(windower.ffxi.get_player().index or 0)
1903 if target then -- Please note if you target yourself you will face Due East
1904 local angle = (math.atan2((target.y - self_vector.y), (target.x - self_vector.x))*180/math.pi)*-1
1905 windower.ffxi.turn((angle):radian())
1906 else
1907 windower.add_to_chat(123,"Error: You're not targeting anything to face")
1908 end
1909end
1910
1911function check_ammo()
1912 if state.AutoAmmoMode.value and player.equipment.range and not player.in_combat and not world.in_mog_house then
1913 if rema_ranged_weapons:contains(player.equipment.range) and get_item_next_use(player.equipment.range).usable then
1914 if count_total_ammo(rema_ranged_weapons_ammo[player.equipment.range]) < ammostock then
1915 windower.chat.input('/item "'..player.equipment.range..'" <me>')
1916 add_to_chat(217,"You're low on "..rema_ranged_weapons_ammo[player.equipment.range]..", using "..player.equipment.range..".")
1917 tickdelay = (framerate * 2)
1918 return true
1919 end
1920 end
1921 end
1922 return false
1923end
1924
1925function count_available_ammo(ammo_name)
1926 local ammo_count = 0
1927
1928 for _,n in pairs({"inventory","wardrobe","wardrobe2","wardrobe3","wardrobe4",}) do
1929 if player[n][ammo_name] then
1930 ammo_count = ammo_count + player[n][ammo_name].count
1931 end
1932 end
1933
1934 return ammo_count
1935end
1936
1937function count_total_ammo(ammo_name)
1938 local ammo_count = 0
1939
1940 for _,n in pairs({"inventory","wardrobe","wardrobe2","wardrobe3","wardrobe4","satchel","sack","case"}) do
1941 if player[n][ammo_name] then
1942 ammo_count = ammo_count + player[n][ammo_name].count
1943 end
1944 end
1945
1946 return ammo_count
1947end
1948
1949function check_shadows()
1950 if not state.AutoShadowMode.value or moving or areas.Cities:contains(world.area) then
1951 return false
1952 elseif handle_shadows() then
1953 return true
1954 else
1955 return false
1956 end
1957end
1958
1959function check_rune()
1960
1961 if state.AutoRuneMode.value and (player.main_job == 'RUN' or player.sub_job == 'RUN') then
1962 local abil_recasts = windower.ffxi.get_ability_recasts()
1963
1964 if player.main_job == 'RUN' and (not buffactive[state.RuneElement.value] or buffactive[state.RuneElement.value] < 3) then
1965 if abil_recasts[92] > 0 then return false end
1966 send_command('input /ja "'..state.RuneElement.value..'" <me>')
1967 tickdelay = (framerate * 1.8)
1968 return true
1969
1970 elseif not buffactive[state.RuneElement.value] or buffactive[state.RuneElement.value] < 2 then
1971 if abil_recasts[92] > 0 then return false end
1972 send_command('input /ja "'..state.RuneElement.value..'" <me>')
1973 tickdelay = (framerate * 1.8)
1974 return true
1975
1976 elseif not player.in_combat then
1977 return false
1978
1979 elseif not buffactive['Pflug'] then
1980 if abil_recasts[59] == 0 then
1981 send_command('input /ja "Pflug" <me>')
1982 tickdelay = (framerate * 1.8)
1983 return true
1984 end
1985
1986 elseif not (buffactive['Vallation'] or buffactive['Valiance']) then
1987 if player.main_job == 'RUN' and abil_recasts[113] == 0 then
1988 send_command('input /ja "Valiance" <me>')
1989 tickdelay = (framerate * 1.8)
1990 return true
1991 elseif abil_recasts[23] == 0 then
1992 send_command('input /ja "Vallation" <me>')
1993 tickdelay = (framerate * 1.8)
1994 return true
1995 else
1996 return false
1997 end
1998 else
1999 return false
2000 end
2001
2002 end
2003
2004 return false
2005end
2006
2007function check_ws_acc()
2008 if state.WeaponskillMode.value == 'Match' then
2009 return state.OffenseMode.value
2010 else
2011 return state.WeaponskillMode.value
2012 end
2013end
2014
2015-- Generic combat form handling
2016function update_combat_form()
2017 if sets.engaged[state.Weapons.value] then
2018 state.CombatForm:set(state.Weapons.value)
2019 elseif not player.equipment.main then
2020 if sets.engaged.Unarmed then
2021 state.CombatForm:set('Unarmed')
2022 else
2023 state.CombatForm:reset()
2024 end
2025 elseif player.equipment.main and sets.engaged.DW and not (player.equipment.sub == 'empty' or player.equipment.sub:contains('Grip') or player.equipment.sub:contains('Strap') or res.items[item_name_to_id(player.equipment.sub)].shield_size) then
2026 state.CombatForm:set('DW')
2027 elseif sets.engaged[player.equipment.main] then
2028 state.CombatForm:set(player.equipment.main)
2029 elseif sets.engaged.Fencer and (player.equipment.sub == 'empty' or player.equipment.sub:contains('Grip') or player.equipment.sub:contains('Strap') or res.items[item_name_to_id(player.equipment.sub)].shield_size) then
2030 state.CombatForm:set('Fencer')
2031 else
2032 state.CombatForm:reset()
2033 end
2034end
2035
2036function item_name_to_id(name)
2037 return (player.inventory[name] or player.wardrobe[name] or player.wardrobe2[name] or player.wardrobe3[name] or player.wardrobe4[name] or {}).id
2038end
2039
2040function set_to_item(set)
2041 for k, v in pairs(sets[set]) do
2042 if v ~= empty then
2043 return v
2044 end
2045 end
2046 return false
2047end
2048
2049function item_equipped(item)
2050 for k, v in pairs(player.equipment) do
2051 if v == item then
2052 return true
2053 end
2054 end
2055 return false
2056end
2057
2058function get_current_strategem_count()
2059 -- returns recast in seconds.
2060 local allRecasts = windower.ffxi.get_ability_recasts()
2061 local stratsRecast = allRecasts[231]
2062 local StratagemChargeTimer = 240
2063 local maxStrategems = 1
2064
2065 if player.sub_job == 'SCH' and player.sub_job_level > 29 then
2066 StratagemChargeTimer = 120
2067 elseif player.main_job_level > 89 then
2068 if player.job_points[(res.jobs[player.main_job_id].ens):lower()].jp_spent > 549 then
2069 StratagemChargeTimer = 33
2070 else
2071 StratagemChargeTimer = 48
2072 end
2073 elseif player.main_job_level > 69 then
2074 StratagemChargeTimer = 60
2075 elseif player.main_job_level > 49 then
2076 StratagemChargeTimer = 80
2077 elseif player.main_job_level > 29 then
2078 StratagemChargeTimer = 120
2079 end
2080
2081 if player.sub_job == 'SCH' then
2082 if player.sub_job_level > 29 then
2083 maxStrategems = 2
2084 end
2085 else
2086 maxStrategems = math.floor((player.main_job_level + 10) / 20)
2087 end
2088
2089
2090 local currentCharges = math.floor(maxStrategems - (stratsRecast / StratagemChargeTimer))
2091 return currentCharges
2092end
2093
2094function arts_active()
2095 if buffactive['Light Arts'] or buffactive['Addendum: White'] or buffactive['Dark Arts'] or buffactive['Addendum: Black'] then
2096 return true
2097 else
2098 return false
2099 end
2100end
2101
2102-- Movement Handling
2103lastlocation = 'fff':pack(0,0,0)
2104moving = false
2105wasmoving = false
2106
2107windower.register_event('outgoing chunk',function(id,data,modified,is_injected,is_blocked)
2108 if id == 0x015 then
2109 moving = lastlocation ~= modified:sub(5, 16)
2110 lastlocation = modified:sub(5, 16)
2111
2112 if wasmoving ~= moving and not (midaction() or pet_midaction()) then
2113 send_command('gs c forceequip')
2114 end
2115
2116 if moving and state.RngHelper.value then
2117 send_command('gs rh clear')
2118 end
2119
2120 wasmoving = moving
2121 end
2122end)
2123
2124-- Uninterruptible Handling
2125
2126state.Uninterruptible = M(false, 'Uninterruptible')
2127fixed_pos = ''
2128
2129windower.raw_register_event('outgoing chunk',function(id,original,modified,injected,blocked)
2130 if not blocked then
2131 if id == 0x15 then
2132 if state.Uninterruptible.value and player.status ~= 'Event' and (gearswap.cued_packet or midaction()) and fixed_pos ~= '' then
2133 return original:sub(1,4)..fixed_pos..original:sub(17)
2134 else
2135 fixed_pos = original:sub(5,16)
2136 end
2137 end
2138 end
2139end)