diff options
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs')
19 files changed, 7579 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Modes.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Modes.lua new file mode 100644 index 0000000..4e01890 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Modes.lua @@ -0,0 +1,457 @@ +------------------------------------------------------------------------------------------------------------------- +-- This include library allows use of specially-designed tables for tracking +-- certain types of modes and state. +-- +-- Usage: include('Modes.lua') +-- +-- Construction syntax: +-- +-- 1) Create a new list of mode values. The first value will always be the default. +-- MeleeMode = M{'Normal', 'Acc', 'Att'} -- Construct as a basic table, using braces. +-- MeleeMode = M('Normal', 'Acc', 'Att') -- Pass in a list of strings, using parentheses. +-- +-- Optional: If constructed as a basic table, and the table contains a key value +-- of 'description', that description will be saved for future reference. +-- If a simple list of strings is passed in, no description will be set. +-- +-- MeleeMode = M{['description']='Melee Mode', 'Normal', 'Acc', 'Att'} +-- +-- +-- 2) Create a boolean mode with a specified default value (note parentheses): +-- UseLuzafRing = M(true) +-- UseLuzafRing = M(false) +-- +-- Optional: A string may be provided that will be used as the mode description: +-- UseLuzafRing = M(false, 'description') +-- UseLuzafRing = M(true, 'description') +-- +-- +-- 3) Create a string mode with a specified default value. Construct with a table: +-- CombatWeapon = M{['description']='Melee Mode', ['string']='Dagger'} +-- CombatWeapon = M{['description']='Melee Mode', ['string']=''} +-- +-- +-- +-- Public information fields (all are case-insensitive): +-- +-- 1) m.description -- Get a text description of the mode table, if it's been set. +-- 2) m.current -- Gets the current mode text value. Booleans will return the strings "on" or "off". +-- 3) m.value -- Gets the current mode value. Booleans will return the boolean values of true or false. +-- 3) m.index -- Gets the current index value, or true/false for booleans. +-- +-- Typically use m.current when using the mode to create a set name, and m.value when +-- testing the mode value in conditional rules. +-- +-- Public class functions: +-- +-- 1) m:describe(str) -- Sets the description for the mode table to the provided string value. +-- 2) m:options(options) -- Redefine the options for a list mode table. Cannot be used on a boolean table. +-- +-- +-- Public mode manipulation functions: +-- +-- 1) m:cycle() -- Cycles through the list going forwards. Acts as a toggle on boolean mode vars. +-- 2) m:cycleback() -- Cycles through the list going backwards. Acts as a toggle on boolean mode vars. +-- 3) m:toggle() -- Toggles a boolean Mode between true and false. +-- 4) m:set(n) -- Sets the current mode value to n. +-- Note: If m is boolean, n can be boolean true/false, or string of on/off/true/false. +-- Note: If m is boolean and no n is given, this forces m to true. +-- 5) m:unset() -- Sets a boolean mode var to false. +-- 6) m:reset() -- Returns the mode var to its default state. +-- 7) m:default() -- Same as reset() +-- +-- All public functions return the current value after completion. +-- +-- +-- Example usage: +-- +-- sets.MeleeMode.Normal = {} +-- sets.MeleeMode.Acc = {} +-- sets.MeleeMode.Att = {} +-- +-- MeleeMode = M{'Normal', 'Acc', 'Att', ['description']='Melee Mode'} +-- MeleeMode:cycle() +-- equip(sets.engaged[MeleeMode.current]) +-- MeleeMode:options('Normal', 'LowAcc', 'HighAcc') +-- >> Changes the option list, but the description stays 'Melee Mode' +-- +-- +-- sets.LuzafRing.on = {ring2="Luzaf's Ring"} +-- sets.LuzafRing.off = {} +-- +-- UseLuzafRing = M(false) +-- UseLuzafRing:toggle() +-- equip(sets.precast['Phantom Roll'], sets.LuzafRing[UseLuzafRing.value]) +------------------------------------------------------------------------------------------------------------------- + + +_meta = _meta or {} +_meta.M = {} +_meta.M.__class = 'mode' +_meta.M.__methods = {} + + +-- Default constructor for mode tables +-- M{'a', 'b', etc, ['description']='a'} -- defines a mode list, description 'a' +-- M('a', 'b', etc) -- defines a mode list, no description +-- M('a') -- defines a mode list, default 'a' +-- M{['description']='a'} -- defines a mode list, default 'Normal', description 'a' +-- M{} -- defines a mode list, default 'Normal', no description +-- M(false) -- defines a mode boolean, default false, no description +-- M(true) -- defines a mode boolean, default true, no description +-- M(false, 'a') -- defines a mode boolean, default false, description 'a' +-- M(true, 'a') -- defines a mode boolean, default true, description 'a' +-- M() -- defines a mode boolean, default false, no description +function M(t, ...) + local m = {} + m._track = {} + m._track._class = 'mode' + + -- If we're passed a list of strings (that is, the first element is a string), + -- convert them to a table + local args = {...} + if type(t) == 'string' then + t = {[1] = t} + + for ind, val in ipairs(args) do + t[ind+1] = val + end + end + + -- Construct the table that we'll be adding the metadata to + + -- If we have a table of values, it's either a list or a string + if type(t) == 'table' then + -- Save the description, if provided + if t['description'] then + m._track._description = t['description'] + end + + -- If we were given an explicit 'string' field, construct a string mode class. + if t.string and type(t.string) == 'string' then + m._track._type = 'string' + m._track._count = 1 + m._track._default = 'defaultstring' + + if t.string then + m['string'] = t.string + m['defaultstring'] = t.string + end + -- Otherwise put together a standard list mode class. + else + m._track._type = 'list' + m._track._invert = {} + m._track._count = 0 + m._track._default = 1 + + -- Only copy numerically indexed values + for ind, val in ipairs(t) do + m[ind] = val + m._track._invert[val] = ind + m._track._count = ind + end + + if m._track._count == 0 then + m[1] = 'Normal' + m._track._invert['Normal'] = 1 + m._track._count = 1 + end + end + -- If the first argument is a bool, construct a boolean mode class. + elseif type(t) == 'boolean' or t == nil then + m._track._type = 'boolean' + m._track._count = 2 + m._track._default = t or false + m._track._description = args[1] + -- Text lookups for bool values + m[true] = 'on' + m[false] = 'off' + else + -- Construction failure + error("Unable to construct a mode table with the provided parameters.", 2) + end + + -- Initialize current value to the default. + m._track._current = m._track._default + + return setmetatable(m, _meta.M) +end + +-------------------------------------------------------------------------- +-- Metamethods +-- Functions that will be used as metamethods for the class +-------------------------------------------------------------------------- + +-- Handler for __index when trying to access the current mode value. +-- Handles indexing 'current' or 'value' keys. +_meta.M.__index = function(m, k) + if type(k) == 'string' then + local lk = k:lower() + if lk == 'current' then + return m[m._track._current] + elseif lk == 'value' then + if m._track._type == 'boolean' then + return m._track._current + else + return m[m._track._current] + end + elseif lk == 'has_value' then + return _meta.M.__methods.f_has_value(m) + elseif lk == 'default' then + if m._track._type == 'boolean' then + return m._track._default + else + return m[m._track._default] + end + elseif lk == 'description' then + return m._track._description + elseif lk == 'index' then + return m._track._current + elseif m._track[lk] then + return m._track[lk] + elseif m._track['_'..lk] then + return m._track['_'..lk] + else + return _meta.M.__methods[lk] + end + end +end + +-- Tostring handler for printing out the table and its current state. +_meta.M.__tostring = function(m) + local res = '' + if m._track._description then + res = res .. m._track._description .. ': ' + end + + if m._track._type == 'list' then + res = res .. '{' + for k,v in ipairs(m) do + res = res..tostring(v) + if m[k+1] ~= nil then + res = res..', ' + end + end + res = res..'}' + elseif m._track._type == 'string' then + res = res .. 'String' + else + res = res .. 'Boolean' + end + + res = res .. ' ('..tostring(m.Current).. ')' + + -- Debug addition + --res = res .. ' [' .. m._track._type .. '/' .. tostring(m._track._current) .. ']' + + return res +end + +-- Length handler for the # value lookup. +_meta.M.__len = function(m) + return m._track._count +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that can be used as public methods for manipulating the class. +-------------------------------------------------------------------------- + +-- Function to set the table's description. +_meta.M.__methods['describe'] = function(m, str) + if type(str) == 'string' then + m._track._description = str + else + error("Invalid argument type: " .. type(str), 2) + end +end + +-- Function to change the list of options available. +-- Leaves the description intact. +-- Cannot be used on boolean classes. +_meta.M.__methods['options'] = function(m, ...) + if m._track._type ~= 'list' then + error("Can only revise the options list for a list mode class.", 2) + end + + local options = {...} + -- Always include a default option if nothing else is given. + if #options == 0 then + options = {'Normal'} + end + + -- Zero-out existing values and clear the tracked inverted list + -- and member count. + for key,val in ipairs(m) do + m[key] = nil + end + m._track._invert = {} + m._track._count = 0 + + -- Insert in new data. + for key,val in ipairs(options) do + m[key] = val + m._track._invert[val] = key + m._track._count = key + end + + m._track._current = m._track._default +end + + +-- Function to test whether the list table contains the specified value. +_meta.M.__methods['contains'] = function(m, str) + if m._track._invert then + if type(str) == 'string' then + return (m._track._invert[str] ~= nil) + else + error("Invalid argument type: " .. type(str), 2) + end + else + error("Cannot test for containment on a " .. m._track._type .. " mode class.", 2) + end +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that will be used as public methods for manipulating state. +-------------------------------------------------------------------------- + +-- Cycle forwards through the list +_meta.M.__methods['cycle'] = function(m) + if m._track._type == 'list' then + m._track._current = (m._track._current % m._track._count) + 1 + elseif m._track._type == 'boolean' then + m:toggle() + end + + return m.Current +end + +-- Cycle backwards through the list +_meta.M.__methods['cycleback'] = function(m) + if m._track._type == 'list' then + m._track._current = m._track._current - 1 + if m._track._current < 1 then + m._track._current = m._track._count + end + elseif m._track._type == 'boolean' then + m:toggle() + end + + return m.Current +end + +-- Toggle a boolean value +_meta.M.__methods['toggle'] = function(m) + if m._track._type == 'boolean' then + m._track._current = not m._track._current + else + error("Can only toggle a boolean mode.", 2) + end + + return m.Current +end + + +-- Set the current value +_meta.M.__methods['set'] = function(m, val) + if m._track._type == 'boolean' then + if val == nil then + m._track._current = true + elseif type(val) == 'boolean' then + m._track._current = val + elseif type(val) == 'string' then + val = val:lower() + if val == 'on' or val == 'true' then + m._track._current = true + elseif val == 'off' or val == 'false' then + m._track._current = false + else + error("Unrecognized value: "..tostring(val), 2) + end + else + error("Unrecognized value type: "..type(val), 2) + end + elseif m._track._type == 'list' then + if not val then + error("List variable cannot be set to nil.", 2) + end + if m._track._invert[val] then + m._track._current = m._track._invert[val] + else + local found = false + for v, ind in pairs(m._track._invert) do + if val:lower() == v:lower() then + m._track._current = ind + found = true + break + end + end + + if not found then + error("Unknown mode value: " .. tostring(val), 2) + end + end + elseif m._track._type == 'string' then + if type(val) == 'string' then + m._track._current = 'string' + m.string = val + else + error("Unrecognized value type: "..type(val), 2) + end + end + + return m.Current +end + + +-- Forces a boolean mode to false, or a string to an empty string. +_meta.M.__methods['unset'] = function(m) + if m._track._type == 'boolean' then + m._track._current = false + elseif m._track._type == 'string' then + m._track._current = 'string' + m.string = '' + else + error("Cannot unset a list mode class.", 2) + end + + return m.Current +end + + +-- Reset to the default value +_meta.M.__methods['reset'] = function(m) + m._track._current = m._track._default + + return m.Current +end + + +-- Check to be sure that the mode var has a valid (for its type) value. +-- String vars must be non-empty. +-- List vars must not be empty strings, or the word 'none'. +-- Boolean are always considered valid (can only be true or false). +_meta.M.__methods['f_has_value'] = function(m) + local cur = m.value + if m._track._type == 'string' then + if cur and cur ~= '' then + return true + else + return false + end + elseif m._track._type == 'boolean' then + return true + else + if not cur or cur == '' or cur:lower() == 'none' then + return false + else + return true + end + end +end + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Globals.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Globals.lua new file mode 100644 index 0000000..d7b2e04 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Globals.lua @@ -0,0 +1,114 @@ +------------------------------------------------------------------------------------------------------------------- +-- Tables and functions for commonly-referenced gear that job files may need, but +-- doesn't belong in the global Mote-Include file since they'd get clobbered on each +-- update. +-- Creates the 'gear' table for reference in other files. +-- +-- Note: Function and table definitions should be added to user, but references to +-- the contained tables via functions (such as for the obi function, below) use only +-- the 'gear' table. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Modify the sets table. Any gear sets that are added to the sets table need to +-- be defined within this function, because sets isn't available until after the +-- include is complete. It is called at the end of basic initialization in Mote-Include. +------------------------------------------------------------------------------------------------------------------- + +function define_global_sets() + -- Special gear info that may be useful across jobs. + + -- Staffs + gear.Staff = {} + gear.Staff.HMP = 'Chatoyant Staff' + gear.Staff.PDT = 'Earth Staff' + + -- Dark Rings + gear.DarkRing = {} + gear.DarkRing.physical = {name="Dark Ring",augments={'Magic dmg. taken -3%','Spell interruption rate down -5%','Phys. dmg. taken -6%'}} + gear.DarkRing.magical = {name="Dark Ring", augments={'Magic dmg. taken -6%','Breath dmg. taken -5%'}} + + -- Default items for utility gear values. + gear.default.weaponskill_neck = "Asperity Necklace" + gear.default.weaponskill_waist = "Caudata Belt" + gear.default.obi_waist = "Cognition Belt" + gear.default.obi_back = "Toro Cape" + gear.default.obi_ring = "Strendu Ring" + gear.default.fastcast_staff = "" + gear.default.recast_staff = "" +end + +------------------------------------------------------------------------------------------------------------------- +-- Functions to set user-specified binds, generally on load and unload. +-- Kept separate from the main include so as to not get clobbered when the main is updated. +------------------------------------------------------------------------------------------------------------------- + +-- Function to bind GearSwap binds when loading a GS script. +function global_on_load() + send_command('bind f9 gs c cycle OffenseMode') + send_command('bind ^f9 gs c cycle HybridMode') + send_command('bind !f9 gs c cycle RangedMode') + send_command('bind @f9 gs c cycle WeaponskillMode') + send_command('bind f10 gs c set DefenseMode Physical') + send_command('bind ^f10 gs c cycle PhysicalDefenseMode') + send_command('bind !f10 gs c toggle Kiting') + send_command('bind f11 gs c set DefenseMode Magical') + send_command('bind ^f11 gs c cycle CastingMode') + send_command('bind f12 gs c update user') + send_command('bind ^f12 gs c cycle IdleMode') + send_command('bind !f12 gs c reset DefenseMode') + + send_command('bind ^- gs c toggle selectnpctargets') + send_command('bind ^= gs c cycle pctargetmode') +end + +-- Function to revert binds when unloading. +function global_on_unload() + send_command('unbind f9') + send_command('unbind ^f9') + send_command('unbind !f9') + send_command('unbind @f9') + send_command('unbind f10') + send_command('unbind ^f10') + send_command('unbind !f10') + send_command('unbind f11') + send_command('unbind ^f11') + send_command('unbind !f11') + send_command('unbind f12') + send_command('unbind ^f12') + send_command('unbind !f12') + + send_command('unbind ^-') + send_command('unbind ^=') +end + +------------------------------------------------------------------------------------------------------------------- +-- Global event-handling functions. +------------------------------------------------------------------------------------------------------------------- + +-- Global intercept on precast. +function user_precast(spell, action, spellMap, eventArgs) + cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + refine_waltz(spell, action, spellMap, eventArgs) +end + +-- Global intercept on midcast. +function user_midcast(spell, action, spellMap, eventArgs) + -- Default base equipment layer of fast recast. + if spell.action_type == 'Magic' and sets.midcast and sets.midcast.FastRecast then + equip(sets.midcast.FastRecast) + end +end + +-- Global intercept on buff change. +function user_buff_change(buff, gain, eventArgs) + -- Create a timer when we gain weakness. Remove it when weakness is gone. + if buff:lower() == 'weakness' then + if gain then + send_command('timers create "Weakness" 300 up abilities/00255.png') + else + send_command('timers delete "Weakness"') + end + end +end + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Include.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Include.lua new file mode 100644 index 0000000..e5527f0 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Include.lua @@ -0,0 +1,1125 @@ +------------------------------------------------------------------------------------------------------------------- +-- Common variables and functions to be included in job scripts, for general default handling. +-- +-- Include this file in the get_sets() function with the command: +-- include('Mote-Include.lua') +-- +-- It will then automatically run its own init_include() function. +-- +-- IMPORTANT: This include requires supporting include files: +-- Mote-Utility +-- Mote-Mappings +-- Mote-SelfCommands +-- Mote-Globals +-- +-- Place the include() directive at the start of a job's get_sets() function. +-- +-- Included variables and functions are considered to be at the same scope level as +-- the job script itself, and can be used as such. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Initialization function that defines variables to be used. +-- These are accessible at the including job lua script's scope. +-- +-- Auto-initialize after defining this function. +------------------------------------------------------------------------------------------------------------------- + +current_mote_include_version = 2 + +function init_include() + -- Used to define various types of data mappings. These may be used in the initialization, so load it up front. + include('Mote-Mappings') + + -- Modes is the include for a mode-tracking variable class. Used for state vars, below. + include('Modes') + + -- Var for tracking state values + state = {} + + -- General melee offense/defense modes, allowing for hybrid set builds, as well as idle/resting/weaponskill. + -- This just defines the vars and sets the descriptions. List modes with no values automatically + -- get assigned a 'Normal' default value. + state.OffenseMode = M{['description'] = 'Offense Mode'} + state.HybridMode = M{['description'] = 'Hybrid Mode'} + state.RangedMode = M{['description'] = 'Ranged Mode'} + state.WeaponskillMode = M{['description'] = 'Weaponskill Mode'} + state.CastingMode = M{['description'] = 'Casting Mode'} + state.IdleMode = M{['description'] = 'Idle Mode'} + state.RestingMode = M{['description'] = 'Resting Mode'} + + state.DefenseMode = M{['description'] = 'Defense Mode', 'None', 'Physical', 'Magical'} + state.PhysicalDefenseMode = M{['description'] = 'Physical Defense Mode', 'PDT'} + state.MagicalDefenseMode = M{['description'] = 'Magical Defense Mode', 'MDT'} + + state.Kiting = M(false, 'Kiting') + state.SelectNPCTargets = M(false, 'Select NPC Targets') + state.PCTargetMode = M{['description'] = 'PC Target Mode', 'default', 'stpt', 'stal', 'stpc'} + + state.EquipStop = M{['description'] = 'Stop Equipping Gear', 'off', 'precast', 'midcast', 'pet_midcast'} + + state.CombatWeapon = M{['description']='Combat Weapon', ['string']=''} + state.CombatForm = M{['description']='Combat Form', ['string']=''} + + -- Non-mode vars that are used for state tracking. + state.MaxWeaponskillDistance = 0 + state.Buff = {} + + -- Classes describe a 'type' of action. They are similar to state, but + -- may have any free-form value, or describe an entire table of mapped values. + classes = {} + -- Basic spell mappings are based on common spell series. + -- EG: 'Cure' for Cure, Cure II, Cure III, Cure IV, Cure V, or Cure VI. + classes.SpellMaps = spell_maps + -- List of spells and spell maps that don't benefit from greater skill (though + -- they may benefit from spell-specific augments, such as improved regen or refresh). + -- Spells that fall under this category will be skipped when searching for + -- spell.skill sets. + classes.NoSkillSpells = no_skill_spells_list + classes.SkipSkillCheck = false + -- Custom, job-defined class, like the generic spell mappings. + -- Takes precedence over default spell maps. + -- Is reset at the end of each spell casting cycle (ie: at the end of aftercast). + classes.JAMode = nil + classes.CustomClass = nil + -- Custom groups used for defining melee and idle sets. Persists long-term. + classes.CustomMeleeGroups = L{} + classes.CustomRangedGroups = L{} + classes.CustomIdleGroups = L{} + classes.CustomDefenseGroups = L{} + + -- Class variables for time-based flags + classes.Daytime = false + classes.DuskToDawn = false + + + -- Var for tracking misc info + info = {} + options = {} + + -- Special control flags. + mote_vars = {} + mote_vars.set_breadcrumbs = L{} + mote_vars.res_buffs = S{} + for index,struct in pairs(gearswap.res.buffs) do + mote_vars.res_buffs:add(struct.en) + end + + -- Sub-tables within the sets table that we expect to exist, and are annoying to have to + -- define within each individual job file. We can define them here to make sure we don't + -- have to check for existence. The job file should be including this before defining + -- any sets, so any changes it makes will override these anyway. + sets.precast = {} + sets.precast.FC = {} + sets.precast.JA = {} + sets.precast.WS = {} + sets.precast.RA = {} + sets.midcast = {} + sets.midcast.RA = {} + sets.midcast.Pet = {} + sets.idle = {} + sets.resting = {} + sets.engaged = {} + sets.defense = {} + sets.buff = {} + + gear = {} + gear.default = {} + + gear.ElementalGorget = {name=""} + gear.ElementalBelt = {name=""} + gear.ElementalObi = {name=""} + gear.ElementalCape = {name=""} + gear.ElementalRing = {name=""} + gear.FastcastStaff = {name=""} + gear.RecastStaff = {name=""} + + + -- Load externally-defined information (info that we don't want to change every time this file is updated). + + -- Used to define misc utility functions that may be useful for this include or any job files. + include('Mote-Utility') + + -- Used for all self-command handling. + include('Mote-SelfCommands') + + -- Include general user globals, such as custom binds or gear tables. + -- Load Mote-Globals first, followed by User-Globals, followed by <character>-Globals. + -- Any functions re-defined in the later includes will overwrite the earlier versions. + include('Mote-Globals') + optional_include({'user-globals.lua'}) + optional_include({player.name..'-globals.lua'}) + + -- *-globals.lua may define additional sets to be added to the local ones. + if define_global_sets then + define_global_sets() + end + + -- Global default binds (either from Mote-Globals or user-globals) + (binds_on_load or global_on_load)() + + -- Load a sidecar file for the job (if it exists) that may re-define init_gear_sets and file_unload. + load_sidecar(player.main_job) + + -- General var initialization and setup. + if job_setup then + job_setup() + end + + -- User-specific var initialization and setup. + if user_setup then + user_setup() + end + + -- Load up all the gear sets. + init_gear_sets() +end + +if not mote_include_version or mote_include_version < current_mote_include_version then + add_to_chat(123,'Warning: Your job file is out of date. Please update to the latest repository baseline.') + add_to_chat(123,'For details, visit https://github.com/Kinematics/GearSwap-Jobs/wiki/Upgrading') + rev = mote_include_version or 1 + include_path('rev' .. tostring(rev)) + include('Mote-Include') + return +end + +-- Auto-initialize the include +init_include() + +-- Called when this job file is unloaded (eg: job change) +-- Conditional definition so that it doesn't overwrite explicit user +-- versions of this function. +if not file_unload then + file_unload = function() + if user_unload then + user_unload() + elseif job_file_unload then + job_file_unload() + end + _G[(binds_on_unload and 'binds_on_unload') or 'global_on_unload']() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Generalized functions for handling precast/midcast/aftercast for player-initiated actions. +-- This depends on proper set naming. +-- Global hooks can be written as user_xxx() to override functions at a global level. +-- Each job can override any of these general functions using job_xxx() hooks. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------ +-- Generic function to map a set processing order to all action events. +------------------------------------------------------------------------ + + +-- Process actions in a specific order of events: +-- Filter - filter_xxx() functions determine whether to run any of the code for this action. +-- Global - user_xxx() functions get called first. Define in Mote-Globals or User-Globals. +-- Local - job_xxx() functions get called next. Define in JOB.lua file. +-- Default - default_xxx() functions get called next. Defined in this file. +-- Cleanup - cleanup_xxx() functions always get called before exiting. +-- +-- Parameters: +-- spell - standard spell table passed in by GearSwap +-- action - string defining the function mapping to use (precast, midcast, etc) +function handle_actions(spell, action) + -- Init an eventArgs that allows cancelling. + local eventArgs = {handled = false, cancel = false} + + mote_vars.set_breadcrumbs:clear() + + -- Get the spell mapping, since we'll be passing it to various functions and checks. + local spellMap = get_spell_map(spell) + + -- General filter checks to see whether this function should be run. + -- If eventArgs.cancel is set, cancels this function, not the spell. + if _G['filter_'..action] then + _G['filter_'..action](spell, spellMap, eventArgs) + end + + -- If filter didn't cancel it, process user and default actions. + if not eventArgs.cancel then + -- Global user handling of this action + if _G['user_'..action] then + _G['user_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Job-specific handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['job_'..action] then + _G['job_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Default handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['default_'..action] then + _G['default_'..action](spell, spellMap) + display_breadcrumbs(spell, spellMap, action) + end + + -- Global post-handling of this action + if not eventArgs.cancel and _G['user_post_'..action] then + _G['user_post_'..action](spell, action, spellMap, eventArgs) + end + + -- Job-specific post-handling of this action + if not eventArgs.cancel and _G['job_post_'..action] then + _G['job_post_'..action](spell, action, spellMap, eventArgs) + end + end + + -- Cleanup once this action is done + if _G['cleanup_'..action] then + _G['cleanup_'..action](spell, spellMap, eventArgs) + end +end + + +-------------------------------------- +-- Action hooks called by GearSwap. +-------------------------------------- + +function pretarget(spell) + handle_actions(spell, 'pretarget') +end + +function precast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = true + end + handle_actions(spell, 'precast') +end + +function midcast(spell) + handle_actions(spell, 'midcast') +end + +function aftercast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = not spell.interrupted or buffactive[spell.english] or false + end + handle_actions(spell, 'aftercast') +end + +function pet_midcast(spell) + handle_actions(spell, 'pet_midcast') +end + +function pet_aftercast(spell) + handle_actions(spell, 'pet_aftercast') +end + +-------------------------------------- +-- Default code for each action. +-------------------------------------- + +function default_pretarget(spell, spellMap) + auto_change_target(spell, spellMap) +end + +function default_precast(spell, spellMap) + equip(get_precast_set(spell, spellMap)) +end + +function default_midcast(spell, spellMap) + equip(get_midcast_set(spell, spellMap)) +end + +function default_aftercast(spell, spellMap) + if not pet_midaction() then + handle_equipping_gear(player.status) + end +end + +function default_pet_midcast(spell, spellMap) + equip(get_pet_midcast_set(spell, spellMap)) +end + +function default_pet_aftercast(spell, spellMap) + handle_equipping_gear(player.status) +end + +-------------------------------------- +-- Filters for each action. +-- Set eventArgs.cancel to true to stop further processing. +-- May show notification messages, but should not do any processing here. +-------------------------------------- + +function filter_midcast(spell, spellMap, eventArgs) + if state.EquipStop.value == 'precast' then + eventArgs.cancel = true + end +end + +function filter_aftercast(spell, spellMap, eventArgs) + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' or state.EquipStop.value == 'pet_midcast' then + eventArgs.cancel = true + elseif spell.name == 'Unknown Interrupt' then + eventArgs.cancel = true + end +end + +function filter_pet_midcast(spell, spellMap, eventArgs) + -- If we have show_set active for precast or midcast, don't try to equip pet midcast gear. + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' then + add_to_chat(104, 'Show Sets: Pet midcast not equipped.') + eventArgs.cancel = true + end +end + +function filter_pet_aftercast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast or midcast, don't try to equip aftercast gear. + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' or state.EquipStop.value == 'pet_midcast' then + eventArgs.cancel = true + end +end + +-------------------------------------- +-- Cleanup code for each action. +-------------------------------------- + +function cleanup_precast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'precast' then + add_to_chat(104, 'Show Sets: Stopping at precast.') + end +end + +function cleanup_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for midcast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'midcast' then + add_to_chat(104, 'Show Sets: Stopping at midcast.') + end +end + +function cleanup_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + -- If we're in the middle of a pet action, pet_aftercast will handle clearing it. + if not pet_midaction() then + reset_transitory_classes() + end +end + +function cleanup_pet_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for pet midcast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'pet_midcast' then + add_to_chat(104, 'Show Sets: Stopping at pet midcast.') + end +end + +function cleanup_pet_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + reset_transitory_classes() +end + + +-- Clears the values from classes that only exist til the action is complete. +function reset_transitory_classes() + classes.CustomClass = nil + classes.JAMode = nil +end + + + +------------------------------------------------------------------------------------------------------------------- +-- High-level functions for selecting and equipping gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Central point to call to equip gear based on status. +-- Status - Player status that we're using to define what gear to equip. +function handle_equipping_gear(playerStatus, petStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_handle_equipping_gear then + job_handle_equipping_gear(playerStatus, eventArgs) + end + + -- Equip default gear if job didn't handle it. + if not eventArgs.handled then + equip_gear_by_status(playerStatus, petStatus) + end +end + + +-- Function to wrap logic for equipping gear on aftercast, status change, or user update. +-- @param status : The current or new player status that determines what sort of gear to equip. +function equip_gear_by_status(playerStatus, petStatus) + if _global.debug_mode then add_to_chat(123,'Debug: Equip gear for status ['..tostring(status)..'], HP='..tostring(player.hp)) end + + playerStatus = playerStatus or player.status or 'Idle' + + -- If status not defined, treat as idle. + -- Be sure to check for positive HP to make sure they're not dead. + if (playerStatus == 'Idle' or playerStatus == '') and player.hp > 0 then + equip(get_idle_set(petStatus)) + elseif playerStatus == 'Engaged' then + equip(get_melee_set(petStatus)) + elseif playerStatus == 'Resting' then + equip(get_resting_set(petStatus)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on status. +------------------------------------------------------------------------------------------------------------------- + +-- Returns the appropriate idle set based on current state values and location. +-- Set construction order (all of which are optional): +-- sets.idle[idleScope][state.IdleMode][Pet[Engaged]][CustomIdleGroups] +-- +-- Params: +-- petStatus - Optional explicit definition of pet status. +function get_idle_set(petStatus) + local idleSet = sets.idle + + if not idleSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('idle') + + local idleScope + + if buffactive.weakness then + idleScope = 'Weak' + elseif areas.Cities:contains(world.area) then + idleScope = 'Town' + else + idleScope = 'Field' + end + + if idleSet[idleScope] then + idleSet = idleSet[idleScope] + mote_vars.set_breadcrumbs:append(idleScope) + end + + if idleSet[state.IdleMode.current] then + idleSet = idleSet[state.IdleMode.current] + mote_vars.set_breadcrumbs:append(state.IdleMode.current) + end + + if (pet.isvalid or state.Buff.Pet) and idleSet.Pet then + idleSet = idleSet.Pet + petStatus = petStatus or pet.status + mote_vars.set_breadcrumbs:append('Pet') + + if petStatus == 'Engaged' and idleSet.Engaged then + idleSet = idleSet.Engaged + mote_vars.set_breadcrumbs:append('Engaged') + end + end + + for _,group in ipairs(classes.CustomIdleGroups) do + if idleSet[group] then + idleSet = idleSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + idleSet = apply_defense(idleSet) + idleSet = apply_kiting(idleSet) + + if user_customize_idle_set then + idleSet = user_customize_idle_set(idleSet) + end + + if customize_idle_set then + idleSet = customize_idle_set(idleSet) + end + + return idleSet +end + + +-- Returns the appropriate melee set based on current state values. +-- Set construction order (all sets after sets.engaged are optional): +-- sets.engaged[state.CombatForm][state.CombatWeapon][state.OffenseMode][state.DefenseMode][classes.CustomMeleeGroups (any number)] +function get_melee_set() + local meleeSet = sets.engaged + + if not meleeSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('engaged') + + if state.CombatForm.has_value and meleeSet[state.CombatForm.value] then + meleeSet = meleeSet[state.CombatForm.value] + mote_vars.set_breadcrumbs:append(state.CombatForm.value) + end + + if state.CombatWeapon.has_value and meleeSet[state.CombatWeapon.value] then + meleeSet = meleeSet[state.CombatWeapon.value] + mote_vars.set_breadcrumbs:append(state.CombatWeapon.value) + end + + if meleeSet[state.OffenseMode.current] then + meleeSet = meleeSet[state.OffenseMode.current] + mote_vars.set_breadcrumbs:append(state.OffenseMode.current) + end + + if meleeSet[state.HybridMode.current] then + meleeSet = meleeSet[state.HybridMode.current] + mote_vars.set_breadcrumbs:append(state.HybridMode.current) + end + + for _,group in ipairs(classes.CustomMeleeGroups) do + if meleeSet[group] then + meleeSet = meleeSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + meleeSet = apply_defense(meleeSet) + meleeSet = apply_kiting(meleeSet) + + if customize_melee_set then + meleeSet = customize_melee_set(meleeSet) + end + + if user_customize_melee_set then + meleeSet = user_customize_melee_set(meleeSet) + end + + return meleeSet +end + + +-- Returns the appropriate resting set based on current state values. +-- Set construction order: +-- sets.resting[state.RestingMode] +function get_resting_set() + local restingSet = sets.resting + + if not restingSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('resting') + + if restingSet[state.RestingMode.current] then + restingSet = restingSet[state.RestingMode.current] + mote_vars.set_breadcrumbs:append(state.RestingMode.current) + end + + return restingSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on action. +------------------------------------------------------------------------------------------------------------------- + +-- Get the default precast gear set. +function get_precast_set(spell, spellMap) + -- If there are no precast sets defined, bail out. + if not sets.precast then + return {} + end + + local equipSet = sets.precast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('precast') + + -- Determine base sub-table from type of action being performed. + + local cat + + if spell.action_type == 'Magic' then + cat = 'FC' + elseif spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Ability' then + if spell.type == 'WeaponSkill' then + cat = 'WS' + elseif spell.type == 'JobAbility' then + cat = 'JA' + else + -- Allow fallback to .JA table if spell.type isn't found, for all non-weaponskill abilities. + cat = (sets.precast[spell.type] and spell.type) or 'JA' + end + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = false + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + + -- Once we have a named base set, do checks for specialized modes (casting mode, weaponskill mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.type == 'WeaponSkill' then + equipSet = get_weaponskill_set(equipSet, spell, spellMap) + elseif spell.action_type == 'Ability' then + if classes.JAMode and equipSet[classes.JAMode] then + equipSet = equipSet[classes.JAMode] + mote_vars.set_breadcrumbs:append(classes.JAMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Update defintions for element-specific gear that may be used. + set_elemental_gear(spell) + + -- Return whatever we've constructed. + return equipSet +end + + + +-- Get the default midcast gear set. +-- This builds on sets.midcast. +function get_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast then + return {} + end + + local equipSet = sets.midcast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + + -- Determine base sub-table from type of action being performed. + -- Only ranged attacks and items get specific sub-categories here. + + local cat + + if spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = classes.NoSkillSpells:contains(spell.english) + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- After the default checks, do checks for specialized modes (casting mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Return whatever we've constructed. + return equipSet +end + + +-- Get the default pet midcast gear set. +-- This is built in sets.midcast.Pet. +function get_pet_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast or not sets.midcast.Pet then + return {} + end + + local equipSet = sets.midcast.Pet + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + mote_vars.set_breadcrumbs:append('Pet') + + if sets.midcast and sets.midcast.Pet then + classes.SkipSkillCheck = false + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- We can only generally be certain about whether the pet's action is + -- Magic (ie: it cast a spell of its own volition) or Ability (it performed + -- an action at the request of the player). Allow CastinMode and + -- OffenseMode to refine whatever set was selected above. + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.action_type == 'Ability' then + if equipSet[state.OffenseMode.current] then + equipSet = equipSet[state.OffenseMode.current] + mote_vars.set_breadcrumbs:append(state.OffenseMode.current) + end + end + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper weaponskill set. +function get_weaponskill_set(equipSet, spell, spellMap) + -- Custom handling for weaponskills + local ws_mode = state.WeaponskillMode.current + + if ws_mode == 'Normal' then + -- If a particular weaponskill mode isn't specified, see if we have a weaponskill mode + -- corresponding to the current offense mode. If so, use that. + if spell.skill == 'Archery' or spell.skill == 'Marksmanship' then + if state.RangedMode.current ~= 'Normal' and state.WeaponskillMode:contains(state.RangedMode.current) then + ws_mode = state.RangedMode.current + end + else + if state.OffenseMode.current ~= 'Normal' and state.WeaponskillMode:contains(state.OffenseMode.current) then + ws_mode = state.OffenseMode.current + end + end + end + + local custom_wsmode + + -- Allow the job file to specify a preferred weaponskill mode + if get_custom_wsmode then + custom_wsmode = get_custom_wsmode(spell, spellMap, ws_mode) + end + + -- If the job file returned a weaponskill mode, use that. + if custom_wsmode then + ws_mode = custom_wsmode + end + + if equipSet[ws_mode] then + equipSet = equipSet[ws_mode] + mote_vars.set_breadcrumbs:append(ws_mode) + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper ranged set. +function get_ranged_set(equipSet, spell, spellMap) + -- Attach Combat Form and Combat Weapon to set checks + if state.CombatForm.has_value and equipSet[state.CombatForm.value] then + equipSet = equipSet[state.CombatForm.value] + mote_vars.set_breadcrumbs:append(state.CombatForm.value) + end + + if state.CombatWeapon.has_value and equipSet[state.CombatWeapon.value] then + equipSet = equipSet[state.CombatWeapon.value] + mote_vars.set_breadcrumbs:append(state.CombatWeapon.value) + end + + -- Check for specific mode for ranged attacks (eg: Acc, Att, etc) + if equipSet[state.RangedMode.current] then + equipSet = equipSet[state.RangedMode.current] + mote_vars.set_breadcrumbs:append(state.RangedMode.current) + end + + -- Tack on any additionally specified custom groups, if the sets are defined. + for _,group in ipairs(classes.CustomRangedGroups) do + if equipSet[group] then + equipSet = equipSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + return equipSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for optional supplemental gear overriding the default sets defined above. +------------------------------------------------------------------------------------------------------------------- + +-- Function to apply any active defense set on top of the supplied set +-- @param baseSet : The set that any currently active defense set will be applied on top of. (gear set table) +function apply_defense(baseSet) + if state.DefenseMode.current ~= 'None' then + local defenseSet = sets.defense + + defenseSet = sets.defense[state[state.DefenseMode.current .. 'DefenseMode'].current] or defenseSet + + for _,group in ipairs(classes.CustomDefenseGroups) do + defenseSet = defenseSet[group] or defenseSet + end + + if customize_defense_set then + defenseSet = customize_defense_set(defenseSet) + end + + baseSet = set_combine(baseSet, defenseSet) + end + + return baseSet +end + + +-- Function to add kiting gear on top of the base set if kiting state is true. +-- @param baseSet : The gear set that the kiting gear will be applied on top of. +function apply_kiting(baseSet) + if state.Kiting.value then + if sets.Kiting then + baseSet = set_combine(baseSet, sets.Kiting) + end + end + + return baseSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for constructing default gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Get a spell mapping for the spell. +function get_spell_map(spell) + local defaultSpellMap = classes.SpellMaps[spell.english] + local jobSpellMap + + if job_get_spell_map then + jobSpellMap = job_get_spell_map(spell, defaultSpellMap) + end + + return jobSpellMap or defaultSpellMap +end + + +-- Select the equipment set to equip from a given starting table, based on standard +-- selection order: custom class, spell name, spell map, spell skill, and spell type. +-- Spell skill and spell type may further refine their selections based on +-- custom class, spell name and spell map. +function select_specific_set(equipSet, spell, spellMap) + -- Take the determined base equipment set and try to get the simple naming extensions that + -- may apply to it (class, spell name, spell map). + local namedSet = get_named_set(equipSet, spell, spellMap) + + -- If no simple naming sub-tables were found, and we simply got back the original equip set, + -- check for spell.skill and spell.type, then check the simple naming extensions again. + if namedSet == equipSet then + if spell.skill and equipSet[spell.skill] and not classes.SkipSkillCheck then + namedSet = equipSet[spell.skill] + mote_vars.set_breadcrumbs:append(spell.skill) + elseif spell.type and equipSet[spell.type] then + namedSet = equipSet[spell.type] + mote_vars.set_breadcrumbs:append(spell.type) + else + return equipSet + end + + namedSet = get_named_set(namedSet, spell, spellMap) + end + + return namedSet or equipSet +end + + +-- Simple utility function to handle a portion of the equipment set determination. +-- It attempts to select a sub-table of the provided equipment set based on the +-- standard search order of custom class, spell name, and spell map. +-- If no such set is found, it returns the original base set (equipSet) provided. +function get_named_set(equipSet, spell, spellMap) + if equipSet then + if classes.CustomClass and equipSet[classes.CustomClass] then + mote_vars.set_breadcrumbs:append(classes.CustomClass) + return equipSet[classes.CustomClass] + elseif equipSet[spell.english] then + mote_vars.set_breadcrumbs:append(spell.english) + return equipSet[spell.english] + elseif spellMap and equipSet[spellMap] then + mote_vars.set_breadcrumbs:append(spellMap) + return equipSet[spellMap] + else + return equipSet + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Hooks for other events. +------------------------------------------------------------------------------------------------------------------- + +-- Called when the player's subjob changes. +function sub_job_change(newSubjob, oldSubjob) + if user_setup then + user_setup() + end + + if job_sub_job_change then + job_sub_job_change(newSubjob, oldSubjob) + end + + send_command('gs c update') +end + + +-- Called when the player's status changes. +function status_change(newStatus, oldStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + mote_vars.set_breadcrumbs:clear() + + -- Allow a global function to be called on status change. + if user_status_change then + user_status_change(newStatus, oldStatus, eventArgs) + end + + -- Then call individual jobs to handle status change events. + if not eventArgs.handled then + if job_status_change then + job_status_change(newStatus, oldStatus, eventArgs) + end + end + + -- Handle equipping default gear if the job didn't mark this as handled. + if not eventArgs.handled then + handle_equipping_gear(newStatus) + display_breadcrumbs() + end +end + + +-- Called when a player gains or loses a buff. +-- buff == buff gained or lost +-- gain == true if the buff was gained, false if it was lost. +function buff_change(buff, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + if state.Buff[buff] ~= nil then + state.Buff[buff] = gain + end + + -- Allow a global function to be called on buff change. + if user_buff_change then + user_buff_change(buff, gain, eventArgs) + end + + -- Allow jobs to handle buff change events. + if not eventArgs.handled then + if job_buff_change then + job_buff_change(buff, gain, eventArgs) + end + end +end + + +-- Called when a player gains or loses a pet. +-- pet == pet gained or lost +-- gain == true if the pet was gained, false if it was lost. +function pet_change(pet, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to handle pet change events. + if job_pet_change then + job_pet_change(pet, gain, eventArgs) + end + + -- Equip default gear if not handled by the job. + if not eventArgs.handled then + handle_equipping_gear(player.status) + end +end + + +-- Called when the player's pet's status changes. +-- Note that this is also called after pet_change when the pet is released. +-- As such, don't automatically handle gear equips. Only do so if directed +-- to do so by the job. +function pet_status_change(newStatus, oldStatus) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_pet_status_change then + job_pet_status_change(newStatus, oldStatus, eventArgs) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Debugging functions. +------------------------------------------------------------------------------------------------------------------- + +-- This is a debugging function that will print the accumulated set selection +-- breadcrumbs for the default selected set for any given action stage. +function display_breadcrumbs(spell, spellMap, action) + if not _settings.debug_mode then + return + end + + local msg = 'Default ' + + if action and spell then + msg = msg .. action .. ' set selection for ' .. spell.name + end + + if spellMap then + msg = msg .. ' (' .. spellMap .. ')' + end + msg = msg .. ' : ' + + local cons + + for _,name in ipairs(mote_vars.set_breadcrumbs) do + if not cons then + cons = name + else + if name:contains(' ') or name:contains("'") then + cons = cons .. '["' .. name .. '"]' + else + cons = cons .. '.' .. name + end + end + end + + if cons then + if action and cons == ('sets.' .. action) then + msg = msg .. "None" + else + msg = msg .. tostring(cons) + end + add_to_chat(123, msg) + end +end + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Mappings.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Mappings.lua new file mode 100644 index 0000000..47e8b06 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Mappings.lua @@ -0,0 +1,283 @@ +------------------------------------------------------------------------------------------------------------------- +-- Mappings, lists and sets to describe game relationships that aren't easily determinable otherwise. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Elemental mappings for element relationships and certain types of spells and gear. +------------------------------------------------------------------------------------------------------------------- + +-- Basic elements +elements = {} + +elements.list = S{'Light','Dark','Fire','Ice','Wind','Earth','Lightning','Water'} + +elements.weak_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Ice', ['Ice']='Wind', ['Wind']='Earth', ['Earth']='Lightning', + ['Lightning']='Water', ['Water']='Fire'} + +elements.strong_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Water', ['Ice']='Fire', ['Wind']='Ice', ['Earth']='Wind', + ['Lightning']='Earth', ['Water']='Lightning'} + +storms = S{"Aurorastorm", "Voidstorm", "Firestorm", "Sandstorm", "Rainstorm", "Windstorm", "Hailstorm", "Thunderstorm", + "Aurorastorm II", "Voidstorm II", "Firestorm II", "Sandstorm II", "Rainstorm II", "Windstorm II", "Hailstorm II", "Thunderstorm II"} + +elements.storm_of = {['Light']="Aurorastorm", ['Dark']="Voidstorm", ['Fire']="Firestorm", ['Earth']="Sandstorm", + ['Water']="Rainstorm", ['Wind']="Windstorm", ['Ice']="Hailstorm", ['Lightning']="Thunderstorm",['Light']="Aurorastorm II", + ['Dark']="Voidstorm II", ['Fire']="Firestorm II", ['Earth']="Sandstorm II", ['Water']="Rainstorm II", ['Wind']="Windstorm II", + ['Ice']="Hailstorm II", ['Lightning']="Thunderstorm II"} + +spirits = S{"LightSpirit", "DarkSpirit", "FireSpirit", "EarthSpirit", "WaterSpirit", "AirSpirit", "IceSpirit", "ThunderSpirit"} +elements.spirit_of = {['Light']="Light Spirit", ['Dark']="Dark Spirit", ['Fire']="Fire Spirit", ['Earth']="Earth Spirit", + ['Water']="Water Spirit", ['Wind']="Air Spirit", ['Ice']="Ice Spirit", ['Lightning']="Thunder Spirit"} + +runes = S{'Lux', 'Tenebrae', 'Ignis', 'Gelus', 'Flabra', 'Tellus', 'Sulpor', 'Unda'} +elements.rune_of = {['Light']='Lux', ['Dark']='Tenebrae', ['Fire']='Ignis', ['Ice']='Gelus', ['Wind']='Flabra', + ['Earth']='Tellus', ['Lightning']='Sulpor', ['Water']='Unda'} + +elements.obi_of = {['Light']='Hachirin-no-obi', ['Dark']='Hachirin-no-obi', ['Fire']='Hachirin-no-obi', ['Ice']='Hachirin-no-obi', ['Wind']='Hachirin-no-obi', + ['Earth']='Hachirin-no-obi', ['Lightning']='Hachirin-no-obi', ['Water']='Hachirin-no-obi'} + +elements.gorget_of = {['Light']='Fotia Gorget', ['Dark']='Fotia Gorget', ['Fire']='Fotia Gorget', ['Ice']='Fotia Gorget', + ['Wind']='Fotia Gorget', ['Earth']='Fotia Gorget', ['Lightning']='Fotia Gorget', ['Water']='Fotia Gorget'} + +elements.belt_of = {['Light']='Fotia Belt', ['Dark']='Fotia Belt', ['Fire']='Fotia Belt', ['Ice']='Fotia Belt', + ['Wind']='Fotia Belt', ['Earth']='Fotia Belt', ['Lightning']='Fotia Belt', ['Water']='Fotia Belt'} + +elements.fastcast_staff_of = {['Light']='Arka I', ['Dark']='Xsaeta I', ['Fire']='Atar I', ['Ice']='Vourukasha I', + ['Wind']='Vayuvata I', ['Earth']='Vishrava I', ['Lightning']='Apamajas I', ['Water']='Haoma I', ['Thunder']='Apamajas I'} + +elements.recast_staff_of = {['Light']='Arka II', ['Dark']='Xsaeta II', ['Fire']='Atar II', ['Ice']='Vourukasha II', + ['Wind']='Vayuvata II', ['Earth']='Vishrava II', ['Lightning']='Apamajas II', ['Water']='Haoma II', ['Thunder']='Apamajas II'} + +elements.perpetuance_staff_of = {['Light']='Arka III', ['Dark']='Xsaeta III', ['Fire']='Atar III', ['Ice']='Vourukasha III', + ['Wind']='Vayuvata III', ['Earth']='Vishrava III', ['Lightning']='Apamajas III', ['Water']='Haoma III', ['Thunder']='Apamajas III'} + + +-- Elements for skillchain names +skillchain_elements = {} +skillchain_elements.Light = S{'Light','Fire','Wind','Lightning'} +skillchain_elements.Darkness = S{'Dark','Ice','Earth','Water'} +skillchain_elements.Fusion = S{'Light','Fire'} +skillchain_elements.Fragmentation = S{'Wind','Lightning'} +skillchain_elements.Distortion = S{'Ice','Water'} +skillchain_elements.Gravitation = S{'Dark','Earth'} +skillchain_elements.Transfixion = S{'Light'} +skillchain_elements.Compression = S{'Dark'} +skillchain_elements.Liquification = S{'Fire'} +skillchain_elements.Induration = S{'Ice'} +skillchain_elements.Detonation = S{'Wind'} +skillchain_elements.Scission = S{'Earth'} +skillchain_elements.Impaction = S{'Lightning'} +skillchain_elements.Reverberation = S{'Water'} + + +------------------------------------------------------------------------------------------------------------------- +-- Mappings for weaponskills +------------------------------------------------------------------------------------------------------------------- + +-- REM weapons and their corresponding weaponskills +data = {} +data.weaponskills = {} +data.weaponskills.relic = { + ["Spharai"] = "Final Heaven", + ["Mandau"] = "Mercy Stroke", + ["Excalibur"] = "Knights of Round", + ["Ragnarok"] = "Scourge", + ["Guttler"] = "Onslaught", + ["Bravura"] = "Metatron Torment", + ["Apocalypse"] = "Catastrophe", + ["Gungnir"] = "Gierskogul", + ["Kikoku"] = "Blade: Metsu", + ["Amanomurakumo"] = "Tachi: Kaiten", + ["Mjollnir"] = "Randgrith", + ["Claustrum"] = "Gates of Tartarus", + ["Annihilator"] = "Coronach", + ["Yoichinoyumi"] = "Namas Arrow"} +data.weaponskills.mythic = { + ["Conqueror"] = "King's Justice", + ["Glanzfaust"] = "Ascetic's Fury", + ["Yagrush"] = "Mystic Boon", + ["Laevateinn"] = "Vidohunir", + ["Murgleis"] = "Death Blossom", + ["Vajra"] = "Mandalic Stab", + ["Burtgang"] = "Atonement", + ["Liberator"] = "Insurgency", + ["Aymur"] = "Primal Rend", + ["Carnwenhan"] = "Mordant Rime", + ["Gastraphetes"] = "Trueflight", + ["Kogarasumaru"] = "Tachi: Rana", + ["Nagi"] = "Blade: Kamu", + ["Ryunohige"] = "Drakesbane", + ["Nirvana"] = "Garland of Bliss", + ["Tizona"] = "Expiacion", + ["Death Penalty"] = "Leaden Salute", + ["Kenkonken"] = "Stringing Pummel", + ["Terpsichore"] = "Pyrrhic Kleos", + ["Tupsimati"] = "Omniscience", + ["Idris"] = "Exudation", + ["Epeolatry"] = "Dimidiation"} +data.weaponskills.empyrean = { + ["Verethragna"] = "Victory Smite", + ["Twashtar"] = "Rudra's Storm", + ["Almace"] = "Chant du Cygne", + ["Caladbolg"] = "Torcleaver", + ["Farsha"] = "Cloudsplitter", + ["Ukonvasara"] = "Ukko's Fury", + ["Redemption"] = "Quietus", + ["Rhongomiant"] = "Camlann's Torment", + ["Kannagi"] = "Blade: Hi", + ["Masamune"] = "Tachi: Fudo", + ["Gambanteinn"] = "Dagann", + ["Hvergelmir"] = "Myrkr", + ["Gandiva"] = "Jishnu's Radiance", + ["Armageddon"] = "Wildfire"} + +-- Weaponskills that can be used at range. +data.weaponskills.ranged = S{"Flaming Arrow", "Piercing Arrow", "Dulling Arrow", "Sidewinder", "Arching Arrow", + "Empyreal Arrow", "Refulgent Arrow", "Apex Arrow", "Namas Arrow", "Jishnu's Radiance", + "Hot Shot", "Split Shot", "Sniper Shot", "Slug Shot", "Heavy Shot", "Detonator", "Last Stand", + "Coronach", "Trueflight", "Leaden Salute", "Wildfire", + "Myrkr"} + +ranged_weaponskills = data.weaponskills.ranged + +------------------------------------------------------------------------------------------------------------------- +-- Spell mappings allow defining a general category or description that each of sets of related +-- spells all fall under. +------------------------------------------------------------------------------------------------------------------- + +spell_maps = { + ['Cure']='Cure',['Cure II']='Cure',['Cure III']='Cure',['Cure IV']='Cure',['Cure V']='Cure',['Cure VI']='Cure', + ['Full Cure']='Cure', + ['Cura']='Curaga',['Cura II']='Curaga',['Cura III']='Curaga', + ['Curaga']='Curaga',['Curaga II']='Curaga',['Curaga III']='Curaga',['Curaga IV']='Curaga',['Curaga V']='Curaga', + -- Status Removal doesn't include Esuna or Sacrifice, since they work differently than the rest + ['Poisona']='StatusRemoval',['Paralyna']='StatusRemoval',['Silena']='StatusRemoval',['Blindna']='StatusRemoval',['Cursna']='StatusRemoval', + ['Stona']='StatusRemoval',['Viruna']='StatusRemoval',['Erase']='StatusRemoval', + ['Barfire']='BarElement',['Barstone']='BarElement',['Barwater']='BarElement',['Baraero']='BarElement',['Barblizzard']='BarElement',['Barthunder']='BarElement', + ['Barfira']='BarElement',['Barstonra']='BarElement',['Barwatera']='BarElement',['Baraera']='BarElement',['Barblizzara']='BarElement',['Barthundra']='BarElement', + ['Raise']='Raise',['Raise II']='Raise',['Raise III']='Raise',['Arise']='Raise', + ['Reraise']='Reraise',['Reraise II']='Reraise',['Reraise III']='Reraise',['Reraise IV']='Reraise', + ['Protect']='Protect',['Protect II']='Protect',['Protect III']='Protect',['Protect IV']='Protect',['Protect V']='Protect', + ['Shell']='Shell',['Shell II']='Shell',['Shell III']='Shell',['Shell IV']='Shell',['Shell V']='Shell', + ['Protectra']='Protectra',['Protectra II']='Protectra',['Protectra III']='Protectra',['Protectra IV']='Protectra',['Protectra V']='Protectra', + ['Shellra']='Shellra',['Shellra II']='Shellra',['Shellra III']='Shellra',['Shellra IV']='Shellra',['Shellra V']='Shellra', + ['Regen']='Regen',['Regen II']='Regen',['Regen III']='Regen',['Regen IV']='Regen',['Regen V']='Regen', + ['Refresh']='Refresh',['Refresh II']='Refresh',['Refresh III']='Refresh', + ['Teleport-Holla']='Teleport',['Teleport-Dem']='Teleport',['Teleport-Mea']='Teleport',['Teleport-Altep']='Teleport',['Teleport-Yhoat']='Teleport', + ['Teleport-Vahzl']='Teleport',['Recall-Pashh']='Teleport',['Recall-Meriph']='Teleport',['Recall-Jugner']='Teleport', + ['Valor Minuet']='Minuet',['Valor Minuet II']='Minuet',['Valor Minuet III']='Minuet',['Valor Minuet IV']='Minuet',['Valor Minuet V']='Minuet', + ["Knight's Minne"]='Minne',["Knight's Minne II"]='Minne',["Knight's Minne III"]='Minne',["Knight's Minne IV"]='Minne',["Knight's Minne V"]='Minne', + ['Advancing March']='March',['Victory March']='March', + ['Sword Madrigal']='Madrigal',['Blade Madrigal']='Madrigal', + ["Hunter's Prelude"]='Prelude',["Archer's Prelude"]='Prelude', + ['Sheepfoe Mambo']='Mambo',['Dragonfoe Mambo']='Mambo', + ['Raptor Mazurka']='Mazurka',['Chocobo Mazurka']='Mazurka', + ['Sinewy Etude']='Etude',['Dextrous Etude']='Etude',['Vivacious Etude']='Etude',['Quick Etude']='Etude',['Learned Etude']='Etude',['Spirited Etude']='Etude',['Enchanting Etude']='Etude', + ['Herculean Etude']='Etude',['Uncanny Etude']='Etude',['Vital Etude']='Etude',['Swift Etude']='Etude',['Sage Etude']='Etude',['Logical Etude']='Etude',['Bewitching Etude']='Etude', + ["Mage's Ballad"]='Ballad',["Mage's Ballad II"]='Ballad',["Mage's Ballad III"]='Ballad', + ["Army's Paeon"]='Paeon',["Army's Paeon II"]='Paeon',["Army's Paeon III"]='Paeon',["Army's Paeon IV"]='Paeon',["Army's Paeon V"]='Paeon',["Army's Paeon VI"]='Paeon', + ['Fire Carol']='Carol',['Ice Carol']='Carol',['Wind Carol']='Carol',['Earth Carol']='Carol',['Lightning Carol']='Carol',['Water Carol']='Carol',['Light Carol']='Carol',['Dark Carol']='Carol', + ['Fire Carol II']='Carol',['Ice Carol II']='Carol',['Wind Carol II']='Carol',['Earth Carol II']='Carol',['Lightning Carol II']='Carol',['Water Carol II']='Carol',['Light Carol II']='Carol',['Dark Carol II']='Carol', + ['Foe Lullaby']='Lullaby',['Foe Lullaby II']='Lullaby',['Horde Lullaby']='Lullaby',['Horde Lullaby II']='Lullaby', + ['Fire Threnody']='Threnody',['Ice Threnody']='Threnody',['Wind Threnody']='Threnody',['Earth Threnody']='Threnody',['Lightning Threnody']='Threnody',['Water Threnody']='Threnody',['Light Threnody']='Threnody',['Dark Threnody']='Threnody', + ['Fire Threnody II']='Threnody',['Ice Threnody II']='Threnody',['Wind Threnody II']='Threnody',['Earth Threnody II']='Threnody',['Lightning Threnody II']='Threnody',['Water Threnody II']='Threnody',['Light Threnody II']='Threnody',['Dark Threnody II']='Threnody', + ['Battlefield Elegy']='Elegy',['Carnage Elegy']='Elegy', + ['Foe Requiem']='Requiem',['Foe Requiem II']='Requiem',['Foe Requiem III']='Requiem',['Foe Requiem IV']='Requiem',['Foe Requiem V']='Requiem',['Foe Requiem VI']='Requiem',['Foe Requiem VII']='Requiem', + ['Utsusemi: Ichi']='Utsusemi',['Utsusemi: Ni']='Utsusemi',['Utsusemi: San']='Utsusemi', + ['Katon: Ichi'] = 'ElementalNinjutsu',['Suiton: Ichi'] = 'ElementalNinjutsu',['Raiton: Ichi'] = 'ElementalNinjutsu', + ['Doton: Ichi'] = 'ElementalNinjutsu',['Huton: Ichi'] = 'ElementalNinjutsu',['Hyoton: Ichi'] = 'ElementalNinjutsu', + ['Katon: Ni'] = 'ElementalNinjutsu',['Suiton: Ni'] = 'ElementalNinjutsu',['Raiton: Ni'] = 'ElementalNinjutsu', + ['Doton: Ni'] = 'ElementalNinjutsu',['Huton: Ni'] = 'ElementalNinjutsu',['Hyoton: Ni'] = 'ElementalNinjutsu', + ['Katon: San'] = 'ElementalNinjutsu',['Suiton: San'] = 'ElementalNinjutsu',['Raiton: San'] = 'ElementalNinjutsu', + ['Doton: San'] = 'ElementalNinjutsu',['Huton: San'] = 'ElementalNinjutsu',['Hyoton: San'] = 'ElementalNinjutsu', + ['Banish']='Banish',['Banish II']='Banish',['Banish III']='Banish',['Banishga']='Banish',['Banishga II']='Banish', + ['Holy']='Holy',['Holy II']='Holy',['Drain']='Drain',['Drain II']='Drain',['Drain III']='Drain',['Aspir']='Aspir',['Aspir II']='Aspir', + ['Absorb-Str']='Absorb',['Absorb-Dex']='Absorb',['Absorb-Vit']='Absorb',['Absorb-Agi']='Absorb',['Absorb-Int']='Absorb',['Absorb-Mnd']='Absorb',['Absorb-Chr']='Absorb', + ['Absorb-Acc']='Absorb',['Absorb-TP']='Absorb',['Absorb-Attri']='Absorb', + ['Enlight']='Enlight',['Enlight II']='Enlight',['Endark']='Endark',['Endark II']='Endark', + ['Burn']='ElementalEnfeeble',['Frost']='ElementalEnfeeble',['Choke']='ElementalEnfeeble',['Rasp']='ElementalEnfeeble',['Shock']='ElementalEnfeeble',['Drown']='ElementalEnfeeble', + ['Pyrohelix']='Helix',['Cryohelix']='Helix',['Anemohelix']='Helix',['Geohelix']='Helix',['Ionohelix']='Helix',['Hydrohelix']='Helix',['Luminohelix']='Helix',['Noctohelix']='Helix', + ['Pyrohelix II']='Helix',['Cryohelix II']='Helix',['Anemohelix II']='Helix',['Geohelix II']='Helix',['Ionohelix II']='Helix',['Hydrohelix II']='Helix',['Luminohelix II']='Helix',['Noctohelix II']='Helix', + ['Firestorm']='Storm',['Hailstorm']='Storm',['Windstorm']='Storm',['Sandstorm']='Storm',['Thunderstorm']='Storm',['Rainstorm']='Storm',['Aurorastorm']='Storm',['Voidstorm']='Storm', + ['Firestorm II']='Storm',['Hailstorm II']='Storm',['Windstorm II']='Storm',['Sandstorm II']='Storm',['Thunderstorm II']='Storm',['Rainstorm II']='Storm',['Aurorastorm II']='Storm',['Voidstorm II']='Storm', + ['Fire Maneuver']='Maneuver',['Ice Maneuver']='Maneuver',['Wind Maneuver']='Maneuver',['Earth Maneuver']='Maneuver',['Thunder Maneuver']='Maneuver', + ['Water Maneuver']='Maneuver',['Light Maneuver']='Maneuver',['Dark Maneuver']='Maneuver', +} + +no_skill_spells_list = S{'Haste', 'Refresh', 'Regen', 'Protect', 'Protectra', 'Shell', 'Shellra', + 'Raise', 'Reraise', 'Sneak', 'Invisible', 'Deodorize'} + + +------------------------------------------------------------------------------------------------------------------- +-- Tables to specify general area groupings. Creates the 'areas' table to be referenced in job files. +-- Zone names provided by world.area/world.zone are currently in all-caps, so defining the same way here. +------------------------------------------------------------------------------------------------------------------- + +areas = {} + +-- City areas for town gear and behavior. +areas.Cities = S{ + "Ru'Lude Gardens", + "Upper Jeuno", + "Lower Jeuno", + "Port Jeuno", + "Port Windurst", + "Windurst Waters", + "Windurst Woods", + "Windurst Walls", + "Heavens Tower", + "Port San d'Oria", + "Northern San d'Oria", + "Southern San d'Oria", + "Port Bastok", + "Bastok Markets", + "Bastok Mines", + "Metalworks", + "Aht Urhgan Whitegate", + "Tavnazian Safehold", + "Nashmau", + "Selbina", + "Mhaura", + "Norg", + "Eastern Adoulin", + "Western Adoulin", + "Kazham", + "Rabao", + "Chocobo Circuit", +} +-- Adoulin areas, where Ionis will grant special stat bonuses. +areas.Adoulin = S{ + "Yahse Hunting Grounds", + "Ceizak Battlegrounds", + "Foret de Hennetiel", + "Morimar Basalt Fields", + "Yorcia Weald", + "Yorcia Weald [U]", + "Cirdas Caverns", + "Cirdas Caverns [U]", + "Marjami Ravine", + "Kamihr Drifts", + "Sih Gates", + "Moh Gates", + "Dho Gates", + "Woh Gates", + "Rala Waterways", + "Rala Waterways [U]", + "Outer Ra'Kaznar", + "Outer Ra'Kaznar [U]" +} + + +------------------------------------------------------------------------------------------------------------------- +-- Lists of certain NPCs. (Not up to date) +------------------------------------------------------------------------------------------------------------------- + +npcs = {} +npcs.Trust = S{'Ajido-Marujido','Aldo','Ayame','Cherukiki','Curilla','D.Shantotto','Elivira','Excenmille', + 'Fablinix','FerreousCoffin','Gadalar','Gessho','Ingrid','IronEater','Joachim','Klara','Kupipi', + 'LehkoHabhoka','LhuMhakaracca','Lion','Luzaf','Maat','MihliAliapoh','Mnejing','Moogle','Mumor', + 'NajaSalaheem','Najelith','Naji','NanaaMihgo','Nashmeira','Noillurie','Ovjang','Prishe','Rainemard', + 'RomaaMihgo','Sakura','Shantotto','StarSibyl','Tenzen','Trion','UkaTotlihn','Ulmia','Valaineral', + 'Volker','Zazarg','Zeid'} + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-SelfCommands.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-SelfCommands.lua new file mode 100644 index 0000000..3015e1f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-SelfCommands.lua @@ -0,0 +1,466 @@ +------------------------------------------------------------------------------------------------------------------- +-- General functions for manipulating state values via self-commands. +-- Only handles certain specific states that we've defined, though it +-- allows the user to hook into the cycle command. +------------------------------------------------------------------------------------------------------------------- + +-- Routing function for general known self_commands. Mappings are at the bottom of the file. +-- Handles splitting the provided command line up into discrete words, for the other functions to use. +function self_command(commandArgs) + local commandArgs = commandArgs + if type(commandArgs) == 'string' then + commandArgs = T(commandArgs:split(' ')) + if #commandArgs == 0 then + return + end + end + + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_self_command then + job_self_command(commandArgs, eventArgs) + end + + if not eventArgs.handled then + -- Of the original command message passed in, remove the first word from + -- the list (it will be used to determine which function to call), and + -- send the remaining words as parameters for the function. + local handleCmd = table.remove(commandArgs, 1) + + if selfCommandMaps[handleCmd] then + selfCommandMaps[handleCmd](commandArgs) + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for manipulating state vars. +------------------------------------------------------------------------------------------------------------------- + +-- Function to set various states to specific values directly. +-- User command format: gs c set [field] [value] +-- If a boolean [field] is used, but not given a [value], it will be set to true. +function handle_set(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Set parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:set(cmdParams[2]) + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + local msg = descrip..' is now '..state_var.current + if state_var == state.DefenseMode and newVal ~= 'None' then + msg = msg .. ' (' .. state[newVal .. 'DefenseMode'].current .. ')' + end + msg = msg .. '.' + + add_to_chat(122, msg) + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Set: Unknown field ['..cmdParams[1]..']') + end + + -- handle string states: CombatForm, CombatWeapon, etc +end + +-- Function to reset values to their defaults. +-- User command format: gs c reset [field] +-- Or: gs c reset all +function handle_reset(cmdParams) + if #cmdParams == 0 then + if _global.debug_mode then add_to_chat(123,'handle_reset: parameter failure: reset type not specified') end + return + end + + local state_var = get_state(cmdParams[1]) + + local oldVal + local newVal + local descrip + + if state_var then + oldVal = state_var.value + state_var:reset() + newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + elseif cmdParams[1]:lower() == 'all' then + for k,v in pairs(state) do + if v._type == 'mode' then + oldVal = v.value + v:reset() + newVal = v.value + + descrip = state_var.description + if descrip and job_state_change then + job_state_change(descrip, newVal, oldVal) + end + end + end + + if job_reset_state then + job_reset_state('all') + end + + if job_state_change then + job_state_change('Reset All') + end + + add_to_chat(122,"All state vars have been reset.") + handle_update({'auto'}) + elseif job_reset_state then + job_reset_state(cmdParams[1]) + else + add_to_chat(123,'Mote-Libs: Reset: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Handle cycling through the options list of a state var. +-- User command format: gs c cycle [field] +function handle_cycle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Cycle parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + if cmdParams[2] and S{'reverse', 'backwards', 'r'}:contains(cmdParams[2]:lower()) then + state_var:cycleback() + else + state_var:cycle() + end + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Cycle: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Handle cycling backwards through the options list of a state var. +-- User command format: gs c cycleback [field] +function handle_cycleback(cmdParams) + cmdParams[2] = 'reverse' + handle_cycle(cmdParams) +end + + +-- Handle toggling of boolean mode vars. +-- User command format: gs c toggle [field] +function handle_toggle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Toggle parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:toggle() + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Toggle: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Function to force a boolean field to false. +-- User command format: gs c unset [field] +function handle_unset(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Unset parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:unset() + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Toggle: Unknown field ['..cmdParams[1]..']') + end +end + +------------------------------------------------------------------------------------------------------------------- + +-- User command format: gs c update [option] +-- Where [option] can be 'user' to display current state. +-- Otherwise, generally refreshes current gear used. +function handle_update(cmdParams) + -- init a new eventArgs + local eventArgs = {handled = false} + + reset_buff_states() + + -- Allow jobs to override this code + if job_update then + job_update(cmdParams, eventArgs) + end + + if not eventArgs.handled then + if handle_equipping_gear then + handle_equipping_gear(player.status) + end + end + + if cmdParams[1] == 'user' then + display_current_state() + end +end + + +-- showtp: equip the current TP set for examination. +function handle_showtp(cmdParams) + local msg = 'Showing current TP set: ['.. state.OffenseMode.value + if state.HybridMode.value ~= 'Normal' then + msg = msg .. '/' .. state.HybridMode.value + end + msg = msg .. ']' + + if #classes.CustomMeleeGroups > 0 then + msg = msg .. ' [' + for i = 1,#classes.CustomMeleeGroups do + msg = msg .. classes.CustomMeleeGroups[i] + if i < #classes.CustomMeleeGroups then + msg = msg .. ', ' + end + end + msg = msg .. ']' + end + + add_to_chat(122, msg) + equip(get_melee_set()) +end + + +-- Minor variation on the GearSwap "gs equip naked" command, that ensures that +-- all slots are enabled before removing gear. +-- Command: "gs c naked" +function handle_naked(cmdParams) + enable('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') + equip(sets.naked) +end + + +------------------------------------------------------------------------------------------------------------------- + +-- Get the state var that matches the requested name. +-- Only returns mode vars. +function get_state(name) + if state[name] then + return state[name]._class == 'mode' and state[name] or nil + else + local l_name = name:lower() + for key,var in pairs(state) do + if key:lower() == l_name then + return var._class == 'mode' and var or nil + end + end + end +end + + +-- Function to reset state.Buff values (called from update). +function reset_buff_states() + if state.Buff then + for buff,present in pairs(state.Buff) do + if mote_vars.res_buffs:contains(buff) then + state.Buff[buff] = buffactive[buff] or false + end + end + end +end + + +-- Function to display the current relevant user state when doing an update. +-- Uses display_current_job_state instead if that is defined in the job lua. +function display_current_state() + local eventArgs = {handled = false} + if display_current_job_state then + display_current_job_state(eventArgs) + end + + if not eventArgs.handled then + local msg = 'Melee' + + if state.CombatForm.has_value then + msg = msg .. ' (' .. state.CombatForm.value .. ')' + end + + msg = msg .. ': ' + + msg = msg .. state.OffenseMode.value + if state.HybridMode.value ~= 'Normal' then + msg = msg .. '/' .. state.HybridMode.value + end + msg = msg .. ', WS: ' .. state.WeaponskillMode.value + + if state.DefenseMode.value ~= 'None' then + msg = msg .. ', Defense: ' .. state.DefenseMode.value .. ' (' .. state[state.DefenseMode.value .. 'DefenseMode'].value .. ')' + end + + if state.Kiting.value == true then + msg = msg .. ', Kiting' + end + + if state.PCTargetMode.value ~= 'default' then + msg = msg .. ', Target PC: '..state.PCTargetMode.value + end + + if state.SelectNPCTargets.value == true then + msg = msg .. ', Target NPCs' + end + + add_to_chat(122, msg) + end + + if state.EquipStop.value ~= 'off' then + add_to_chat(122,'Gear equips are blocked after ['..state.EquipStop.value..']. Use "//gs c reset equipstop" to turn it off.') + end +end + +-- Generic version of this for casters +function display_current_caster_state() + local msg = '' + + if state.OffenseMode.value ~= 'None' then + msg = msg .. 'Melee' + + if state.CombatForm.has_value then + msg = msg .. ' (' .. state.CombatForm.value .. ')' + end + + msg = msg .. ', ' + end + + msg = msg .. 'Casting ['..state.CastingMode.value..'], Idle ['..state.IdleMode.value..']' + + if state.DefenseMode.value ~= 'None' then + msg = msg .. ', ' .. 'Defense: ' .. state.DefenseMode.value .. ' (' .. state[state.DefenseMode.value .. 'DefenseMode'].value .. ')' + end + + if state.Kiting.value == true then + msg = msg .. ', Kiting' + end + + if state.PCTargetMode.value ~= 'default' then + msg = msg .. ', Target PC: '..state.PCTargetMode.value + end + + if state.SelectNPCTargets.value == true then + msg = msg .. ', Target NPCs' + end + + add_to_chat(122, msg) +end + + +------------------------------------------------------------------------------------------------------------------- + +-- Function to show what commands are available, and their syntax. +-- Syntax: gs c help +-- Or: gs c +function handle_help(cmdParams) + if cmdParams[1] and cmdParams[1]:lower():startswith('field') then + print('Predefined Library Fields:') + print('--------------------------') + print('OffenseMode, HybridMode, RangedMode, WeaponskillMode') + print('CastingMode, IdleMode, RestingMode, Kiting') + print('DefenseMode, PhysicalDefenseMode, MagicalDefenseMode') + print('SelectNPCTargets, PCTargetMode') + print('EquipStop (precast, midcast, pet_midcast)') + else + print('Custom Library Self-commands:') + print('-----------------------------') + print('Show TP Set: gs c showtp') + print('Toggle bool: gs c toggle [field]') + print('Cycle list: gs c cycle [field] [(r)everse]') + print('Cycle list back: gs c cycleback [field]') + print('Reset a state: gs c reset [field]') + print('Reset all states: gs c reset all') + print('Set state var: gs c set [field] [value]') + print('Set bool true: gs c set [field]') + print('Set bool false: gs c unset [field]') + print('Remove gear: gs c naked') + print('Show TP Set: gs c showtp') + print('State vars: gs c help field') + end +end + + +-- A function for testing lua code. Called via "gs c test". +function handle_test(cmdParams) + if user_test then + user_test(cmdParams) + elseif job_test then + job_test(cmdParams) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- The below table maps text commands to the above handler functions. +------------------------------------------------------------------------------------------------------------------- + +selfCommandMaps = { + ['toggle'] = handle_toggle, + ['cycle'] = handle_cycle, + ['cycleback']= handle_cycleback, + ['set'] = handle_set, + ['reset'] = handle_reset, + ['unset'] = handle_unset, + ['update'] = handle_update, + ['showtp'] = handle_showtp, + ['naked'] = handle_naked, + ['help'] = handle_help, + ['test'] = handle_test} + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-TreasureHunter.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-TreasureHunter.lua new file mode 100644 index 0000000..d1fd52a --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-TreasureHunter.lua @@ -0,0 +1,297 @@ +------------------------------------------------------------------------------------------------------------------- +-- Utility include for applying and tracking Treasure Hunter effects. +-- +-- Include this if you want a means of applying TH on the first contact +-- with a mob, then resume using normal gear. +-- Thf also has modes to use TH gear for SATA and for keeping it on fulltime. +-- +-- Command: include('Mote-TreasureHunter') +-- Place this in your job_setup() function, or user_setup() function if using +-- a sidecar file, or get_sets() function if your job file isn't based +-- on my includes. +-- Be sure to define your own sets.TreasureHunter gear set after the include. +-- If using a job file based on my includes, simply place it in the +-- standard init_gear_sets() function. +-- +-- If you define TH gear sets for common actions (eg: Provoke, Box Step, etc), +-- then make sure they are accounted for in a th_action_check function +-- (signature: th_action_check(category, param)) in the job file. It's passed +-- category and param value for actions the user takes, and if it returns true, +-- that means that it's considered a valid tagging action. +-- +-- If using this in a job file that isn't based on my includes, you must +-- handle cycling the options values on your own, unless you also include +-- Mote-SelfCommands. +-- +-- The job file must handle the 'update' self-command (gs c update auto). +-- This is automatically handled if using my includes, but must be ensured +-- if being used with a user-built job file. +-- When called, it merely needs to equip standard melee gear for the current +-- configuration. +-- +-- Create a macro or keybind to cycle the Treasure Mode value: +-- gs c cycle TreasureMode +------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------- +-- Setup vars and events when first running the include. +------------------------------------------------------------------------------------------------------------------- + +-- Ensure base tables are defined +options = options or {} +state = state or {} +info = info or {} +state.TreasureMode = M{['description']='Treasure Mode'} + +-- TH mode handling +if player.main_job == 'THF' then + state.TreasureMode:options('None','Tag','SATA','Fulltime') + state.TreasureMode:set('Tag') +else + state.TreasureMode:options('None','Tag') +end + +-- Tracking vars for TH. +info.tagged_mobs = T{} +info.last_player_target_index = 0 +state.th_gear_is_locked = false + +-- Required gear set. Expand this in the job file when defining sets. +sets.TreasureHunter = {} + +-- Event registration is done at the bottom of this file. + + +------------------------------------------------------------------------------------------------------------------- +-- User-callable functions for TH handling utility. +------------------------------------------------------------------------------------------------------------------- + +-- Can call to force a status refresh. +-- Also displays the current tagged mob table if in debug mode. +function th_update(cmdParams, eventArgs) + if (cmdParams and cmdParams[1] == 'user') or not cmdParams then + TH_for_first_hit() + + if _settings.debug_mode then + print_set(info.tagged_mobs, 'Tagged mobs') + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Local functions to support TH handling. +------------------------------------------------------------------------------------------------------------------- + +-- Set locked TH flag to true, and disable relevant gear slots. +function lock_TH() + state.th_gear_is_locked = true + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + disable(slots) +end + + +-- Set locked TH flag to false, and enable relevant gear slots. +function unlock_TH() + state.th_gear_is_locked = false + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + enable(slots) + send_command('gs c update auto') +end + + +-- For any active TH mode, if we haven't already tagged this target, equip TH gear and lock slots until we manage to hit it. +function TH_for_first_hit() + if player.status == 'Engaged' and state.TreasureMode.value ~= 'None' then + if not info.tagged_mobs[player.target.id] then + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..tostring(player.target.id)..'.') end + equip(sets.TreasureHunter) + lock_TH() + elseif state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'Target '..player.target.id..' has been tagged. Unlocking.') end + unlock_TH() + else + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..player.target.id..'. Target has already been tagged.') end + end + else + unlock_TH() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event handlers to allow tracking TH application. +------------------------------------------------------------------------------------------------------------------- + +-- On engaging a mob, attempt to add TH gear. For any other status change, unlock TH gear slots. +function on_status_change_for_th(new_status_id, old_status_id) + if gearswap.gearswap_disabled or T{2,3,4}:contains(old_status_id) or T{2,3,4}:contains(new_status_id) then return end + + local new_status = gearswap.res.statuses[new_status_id].english + local old_status = gearswap.res.statuses[old_status_id].english + + if new_status == 'Engaged' then + if _settings.debug_mode then add_to_chat(123,'Engaging '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + elseif old_status == 'Engaged' then + if _settings.debug_mode and state.th_gear_is_locked then add_to_chat(123,'Disengaging. Unlocking TH.') end + info.last_player_target_index = 0 + unlock_TH() + end +end + + +-- On changing targets, attempt to add TH gear. +function on_target_change_for_th(new_index, old_index) + -- Only care about changing targets while we're engaged, either manually or via current target death. + if player.status == 'Engaged' then + -- If the current player.target is the same as the new mob then we're actually + -- engaged with it. + -- If it's different than the last known mob, then we've actually changed targets. + if player.target.index == new_index and new_index ~= info.last_player_target_index then + if _settings.debug_mode then add_to_chat(123,'Changing target to '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + end + end +end + + +-- On any action event, mark mobs that we tag with TH. Also, update the last time tagged mobs were acted on. +function on_action_for_th(action) + --add_to_chat(123,'cat='..action.category..',param='..action.param) + -- If player takes action, adjust TH tagging information + if state.TreasureMode.value ~= 'None' then + if action.actor_id == player.id then + -- category == 1=melee, 2=ranged, 3=weaponskill, 4=spell, 6=job ability, 14=unblinkable JA + if state.TreasureMode.value == 'Fulltime' or + (state.TreasureMode.value == 'SATA' and (action.category == 1 or ((state.Buff['Sneak Attack'] or state.Buff['Trick Attack']) and action.category == 3))) or + (state.TreasureMode.value == 'Tag' and action.category == 1 and state.th_gear_is_locked) or -- Tagging with a melee hit + (th_action_check and th_action_check(action.category, action.param)) -- Any user-specified tagging actions + then + for index,target in pairs(action.targets) do + if not info.tagged_mobs[target.id] and _settings.debug_mode then + add_to_chat(123,'Mob '..target.id..' hit. Adding to tagged mobs table.') + end + info.tagged_mobs[target.id] = os.time() + end + + if state.th_gear_is_locked then + unlock_TH() + end + end + elseif info.tagged_mobs[action.actor_id] then + -- If mob acts, keep an update of last action time for TH bookkeeping + info.tagged_mobs[action.actor_id] = os.time() + else + -- If anyone else acts, check if any of the targets are our tagged mobs + for index,target in pairs(action.targets) do + if info.tagged_mobs[target.id] then + info.tagged_mobs[target.id] = os.time() + end + end + end + end + + cleanup_tagged_mobs() +end + + +-- Need to use this event handler to listen for deaths in case Battlemod is loaded, +-- because Battlemod blocks the 'action message' event. +-- +-- This function removes mobs from our tracking table when they die. +function on_incoming_chunk_for_th(id, data, modified, injected, blocked) + if id == 0x29 then + local target_id = data:unpack('I',0x09) + local message_id = data:unpack('H',0x19)%32768 + + -- Remove mobs that die from our tagged mobs list. + if info.tagged_mobs[target_id] then + -- 6 == actor defeats target + -- 20 == target falls to the ground + if message_id == 6 or message_id == 20 then + if _settings.debug_mode then add_to_chat(123,'Mob '..target_id..' died. Removing from tagged mobs table.') end + info.tagged_mobs[target_id] = nil + end + end + end +end + + +-- Clear out the entire tagged mobs table when zoning. +function on_zone_change_for_th(new_zone, old_zone) + if _settings.debug_mode then add_to_chat(123,'Zoning. Clearing tagged mobs table.') end + info.tagged_mobs:clear() +end + + +-- Save the existing function, if it exists, and call it after our own handling. +if job_state_change then + job_state_change_via_th = job_state_change +end + + +-- Called if we change any user state fields. +function job_state_change(stateField, newValue, oldValue) + if stateField == 'Treasure Mode' then + if newValue == 'None' and state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'TH Mode set to None. Unlocking gear.') end + unlock_TH() + elseif oldValue == 'None' then + TH_for_first_hit() + end + end + + if job_state_change_via_th then + job_state_change_via_th(stateField, newValue, oldValue) + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Extra utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Remove mobs that we've marked as tagged with TH if we haven't seen any activity from or on them +-- for over 3 minutes. This is to handle deagros, player deaths, or other random stuff where the +-- mob is lost, but doesn't die. +function cleanup_tagged_mobs() + -- If it's been more than 3 minutes since an action on or by a tagged mob, + -- remove them from the tagged mobs list. + local current_time = os.time() + local remove_mobs = S{} + -- Search list and flag old entries. + for target_id,action_time in pairs(info.tagged_mobs) do + local time_since_last_action = current_time - action_time + if time_since_last_action > 180 then + remove_mobs:add(target_id) + if _settings.debug_mode then add_to_chat(123,'Over 3 minutes since last action on mob '..target_id..'. Removing from tagged mobs list.') end + end + end + -- Clean out mobs flagged for removal. + for mob_id,_ in pairs(remove_mobs) do + info.tagged_mobs[mob_id] = nil + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event function registration calls. +-- Can call these now that the above functions have been defined. +------------------------------------------------------------------------------------------------------------------- + +-- Register events to allow us to manage TH application. +windower.register_event('status change', on_status_change_for_th) +windower.register_event('target change', on_target_change_for_th) +windower.raw_register_event('action', on_action_for_th) +windower.raw_register_event('incoming chunk', on_incoming_chunk_for_th) +windower.raw_register_event('zone change', on_zone_change_for_th) + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Utility.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Utility.lua new file mode 100644 index 0000000..ad7a0ff --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-Utility.lua @@ -0,0 +1,663 @@ +------------------------------------------------------------------------------------------------------------------- +-- General utility functions that can be used by any job files. +-- Outside the scope of what the main include file deals with. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Buff utility functions. +------------------------------------------------------------------------------------------------------------------- + +local cancel_spells_to_check = S{'Sneak', 'Stoneskin', 'Spectral Jig', 'Trance', 'Monomi: Ichi', 'Utsusemi: Ichi'} +local cancel_types_to_check = S{'Waltz', 'Samba'} + +-- Function to cancel buffs if they'd conflict with using the spell you're attempting. +-- Requirement: Must have Cancel addon installed and loaded for this to work. +function cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + if cancel_spells_to_check:contains(spell.english) or cancel_types_to_check:contains(spell.type) then + if spell.action_type == 'Ability' then + local abil_recasts = windower.ffxi.get_ability_recasts() + if abil_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Ability waiting on recast.') + eventArgs.cancel = true + return + end + elseif spell.action_type == 'Magic' then + local spell_recasts = windower.ffxi.get_spell_recasts() + if spell_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Spell waiting on recast.') + eventArgs.cancel = true + return + end + end + + if spell.english == 'Spectral Jig' and buffactive.sneak then + cast_delay(0.2) + send_command('cancel sneak') + elseif spell.english == 'Sneak' and spell.target.type == 'SELF' and buffactive.sneak then + send_command('cancel sneak') + elseif spell.english == ('Stoneskin') then + send_command('@wait 1.0;cancel stoneskin') + elseif spell.english:startswith('Monomi') then + send_command('@wait 1.7;cancel sneak') + elseif spell.english == 'Utsusemi: Ichi' then + send_command('@wait 1.7;cancel copy image,copy image (2)') + elseif (spell.english == 'Trance' or spell.type=='Waltz') and buffactive['saber dance'] then + cast_delay(0.2) + send_command('cancel saber dance') + elseif spell.type=='Samba' and buffactive['fan dance'] then + cast_delay(0.2) + send_command('cancel fan dance') + end + end +end + + +-- Some mythics have special durations for level 1 and 2 aftermaths +local special_aftermath_mythics = S{'Tizona', 'Kenkonken', 'Murgleis', 'Yagrush', 'Carnwenhan', 'Nirvana', 'Tupsimati', 'Idris'} + +-- Call from job_precast() to setup aftermath information for custom timers. +function custom_aftermath_timers_precast(spell) + if spell.type == 'WeaponSkill' then + info.aftermath = {} + + local relic_ws = data.weaponskills.relic[player.equipment.main] or data.weaponskills.relic[player.equipment.range] + local mythic_ws = data.weaponskills.mythic[player.equipment.main] or data.weaponskills.mythic[player.equipment.range] + local empy_ws = data.weaponskills.empyrean[player.equipment.main] or data.weaponskills.empyrean[player.equipment.range] + + if not relic_ws and not mythic_ws and not empy_ws then + return + end + + info.aftermath.weaponskill = spell.english + info.aftermath.duration = 0 + + info.aftermath.level = math.floor(player.tp / 1000) + if info.aftermath.level == 0 then + info.aftermath.level = 1 + end + + if spell.english == relic_ws then + info.aftermath.duration = math.floor(0.2 * player.tp) + if info.aftermath.duration < 20 then + info.aftermath.duration = 20 + end + elseif spell.english == empy_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- duration is based on aftermath level + info.aftermath.duration = 30 * info.aftermath.level + elseif spell.english == mythic_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- Assume mythic is lvl 80 or higher, for duration + + if info.aftermath.level == 1 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 90 + elseif info.aftermath.level == 2 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 120 + else + info.aftermath.duration = 180 + end + end + end +end + + +-- Call from job_aftercast() to create the custom aftermath timer. +function custom_aftermath_timers_aftercast(spell) + if not spell.interrupted and spell.type == 'WeaponSkill' and + info.aftermath and info.aftermath.weaponskill == spell.english and info.aftermath.duration > 0 then + + local aftermath_name = 'Aftermath: Lv.'..tostring(info.aftermath.level) + send_command('timers d "Aftermath: Lv.1"') + send_command('timers d "Aftermath: Lv.2"') + send_command('timers d "Aftermath: Lv.3"') + send_command('timers c "'..aftermath_name..'" '..tostring(info.aftermath.duration)..' down abilities/00027.png') + + info.aftermath = {} + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for changing spells and target types in an automatic manner. +------------------------------------------------------------------------------------------------------------------- + +local waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Curing Waltz IV'] = 650, ['Curing Waltz V'] = 800} + +-- Utility function for automatically adjusting the waltz spell being used to match HP needs and TP limits. +-- Handle spell changes before attempting any precast stuff. +function refine_waltz(spell, action, spellMap, eventArgs) + if spell.type ~= 'Waltz' then + return + end + + -- Don't modify anything for Healing Waltz or Divine Waltzes + if spell.english == "Healing Waltz" or spell.english == "Divine Waltz" or spell.english == "Divine Waltz II" then + return + end + + local newWaltz = spell.english + local waltzID + + local missingHP + + -- If curing ourself, get our exact missing HP + if spell.target.type == "SELF" then + missingHP = player.max_hp - player.hp + -- If curing someone in our alliance, we can estimate their missing HP + elseif spell.target.isallymember then + local target = find_player_in_alliance(spell.target.name) + local est_max_hp = target.hp / (target.hpp/100) + missingHP = math.floor(est_max_hp - target.hp) + end + + -- If we have an estimated missing HP value, we can adjust the preferred tier used. + if missingHP ~= nil then + if player.main_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 200 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 600 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + elseif missingHP < 1100 then + newWaltz = 'Curing Waltz III' + waltzID = 192 + elseif missingHP < 1500 then + newWaltz = 'Curing Waltz IV' + waltzID = 193 + else + newWaltz = 'Curing Waltz V' + waltzID = 311 + end + elseif player.sub_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 150 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 300 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + else + newWaltz = 'Curing Waltz III' + waltzID = 192 + end + else + -- Not dnc main or sub; bail out + return + end + end + + local tpCost = waltz_tp_cost[newWaltz] + + local downgrade + + -- Downgrade the spell to what we can afford + if player.tp < tpCost and not buffactive.trance then + --[[ Costs: + Curing Waltz: 200 TP + Curing Waltz II: 350 TP + Curing Waltz III: 500 TP + Curing Waltz IV: 650 TP + Curing Waltz V: 800 TP + Divine Waltz: 400 TP + Divine Waltz II: 800 TP + --]] + + if player.tp < 200 then + add_to_chat(122, 'Insufficient TP ['..tostring(player.tp)..']. Cancelling.') + eventArgs.cancel = true + return + elseif player.tp < 350 then + newWaltz = 'Curing Waltz' + elseif player.tp < 500 then + newWaltz = 'Curing Waltz II' + elseif player.tp < 650 then + newWaltz = 'Curing Waltz III' + elseif player.tp < 800 then + newWaltz = 'Curing Waltz IV' + end + + downgrade = 'Insufficient TP ['..tostring(player.tp)..']. Downgrading to '..newWaltz..'.' + end + + + if newWaltz ~= spell.english then + send_command('@input /ja "'..newWaltz..'" '..tostring(spell.target.raw)) + if downgrade then + add_to_chat(122, downgrade) + end + eventArgs.cancel = true + return + end + + if missingHP and missingHP > 0 then + add_to_chat(122,'Trying to cure '..tostring(missingHP)..' HP using '..newWaltz..'.') + end +end + + +-- Function to allow for automatic adjustment of the spell target type based on preferences. +function auto_change_target(spell, spellMap) + -- Don't adjust targetting for explicitly named targets + if not spell.target.raw:startswith('<') then + return + end + + -- Do not modify target for spells where we get <lastst> or <me>. + if spell.target.raw == ('<lastst>') or spell.target.raw == ('<me>') then + return + end + + -- init a new eventArgs with current values + local eventArgs = {handled = false, PCTargetMode = state.PCTargetMode.value, SelectNPCTargets = state.SelectNPCTargets.value} + + -- Allow the job to do custom handling, or override the default values. + -- They can completely handle it, or set one of the secondary eventArgs vars to selectively + -- override the default state vars. + if job_auto_change_target then + job_auto_change_target(spell, action, spellMap, eventArgs) + end + + -- If the job handled it, we're done. + if eventArgs.handled then + return + end + + local pcTargetMode = eventArgs.PCTargetMode + local selectNPCTargets = eventArgs.SelectNPCTargets + + + local validPlayers = S{'Self', 'Player', 'Party', 'Ally', 'NPC'} + + local intersection = spell.targets * validPlayers + local canUseOnPlayer = not intersection:empty() + + local newTarget + + -- For spells that we can cast on players: + if canUseOnPlayer and pcTargetMode ~= 'default' then + -- Do not adjust targetting for player-targettable spells where the target was <t> + if spell.target.raw ~= ('<t>') then + if pcTargetMode == 'stal' then + -- Use <stal> if possible, otherwise fall back to <stpt>. + if spell.targets.Ally then + newTarget = '<stal>' + elseif spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpt' then + -- Even ally-possible spells are limited to the current party. + if spell.targets.Ally or spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpc' then + -- If it's anything other than a self-only spell, can change to <stpc>. + if spell.targets.Player or spell.targets.Party or spell.targets.Ally or spell.targets.NPC then + newTarget = '<stpc>' + end + end + end + -- For spells that can be used on enemies: + elseif spell.targets and spell.targets.Enemy and selectNPCTargets then + -- Note: this means macros should be written for <t>, and it will change to <stnpc> + -- if the flag is set. It won't change <stnpc> back to <t>. + newTarget = '<stnpc>' + end + + -- If a new target was selected and is different from the original, call the change function. + if newTarget and newTarget ~= spell.target.raw then + change_target(newTarget) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Environment utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Function to get the current weather intensity: 0 for none, 1 for single weather, 2 for double weather. +function get_weather_intensity() + return gearswap.res.weather[world.weather_id].intensity +end + + +-- Returns true if you're in a party solely comprised of Trust NPCs. +-- TODO: Do we need a check to see if we're in a party partly comprised of Trust NPCs? +function is_trust_party() + -- Check if we're solo + if party.count == 1 then + return false + end + + -- Can call a max of 3 Trust NPCs, so parties larger than that are out. + if party.count > 4 then + return false + end + + -- If we're in an alliance, can't be a Trust party. + if alliance[2].count > 0 or alliance[3].count > 0 then + return false + end + + -- Check that, for each party position aside from our own, the party + -- member has one of the Trust NPC names, and that those party members + -- are flagged is_npc. + for i = 2,4 do + if party[i] then + if not npcs.Trust:contains(party[i].name) then + return false + end + if party[i].mob and party[i].mob.is_npc == false then + return false + end + end + end + + -- If it didn't fail any of the above checks, return true. + return true +end + + +-- Call these function with a list of equipment slots to check ('head', 'neck', 'body', etc) +-- Returns true if any of the specified slots are currently encumbered. +-- Returns false if all specified slots are unencumbered. +function is_encumbered(...) + local check_list = {...} + -- Compensate for people passing a table instead of a series of strings. + if type(check_list[1]) == 'table' then + check_list = check_list[1] + end + local check_set = S(check_list) + + for slot_id,slot_name in pairs(gearswap.default_slot_map) do + if check_set:contains(slot_name) then + if gearswap.encumbrance_table[slot_id] then + return true + end + end + end + + return false +end + +------------------------------------------------------------------------------------------------------------------- +-- Elemental gear utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- General handler function to set all the elemental gear for an action. +function set_elemental_gear(spell) + set_elemental_gorget_belt(spell) + set_elemental_obi_cape_ring(spell) + set_elemental_staff(spell) +end + + +-- Set the name field of the predefined gear vars for gorgets and belts, for the specified weaponskill. +function set_elemental_gorget_belt(spell) + if spell.type ~= 'WeaponSkill' then + return + end + + -- Get the union of all the skillchain elements for the weaponskill + local weaponskill_elements = S{}: + union(skillchain_elements[spell.skillchain_a]): + union(skillchain_elements[spell.skillchain_b]): + union(skillchain_elements[spell.skillchain_c]) + + gear.ElementalGorget.name = get_elemental_item_name("gorget", weaponskill_elements) or gear.default.weaponskill_neck or "" + gear.ElementalBelt.name = get_elemental_item_name("belt", weaponskill_elements) or gear.default.weaponskill_waist or "" +end + + +-- Function to get an appropriate obi/cape/ring for the current action. +function set_elemental_obi_cape_ring(spell) + if spell.element == 'None' then + return + end + + local world_elements = S{world.day_element} + if world.weather_element ~= 'None' then + world_elements:add(world.weather_element) + end + + local obi_name = get_elemental_item_name("obi", S{spell.element}, world_elements) + gear.ElementalObi.name = obi_name or gear.default.obi_waist or "" + + if obi_name then + if player.inventory['Twilight Cape'] or player.wardrobe['Twilight Cape'] or player.wardrobe2['Twilight Cape'] or player.wardrobe3['Twilight Cape'] or player.wardrobe4['Twilight Cape'] then + gear.ElementalCape.name = "Twilight Cape" + end + if (player.inventory['Zodiac Ring'] or player.wardrobe['Zodiac Ring'] or player.wardrobe2['Zodiac Ring'] or player.wardrobe3['Zodiac Ring'] or player.wardrobe4['Zodiac Ring']) and spell.english ~= 'Impact' and + not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then + gear.ElementalRing.name = "Zodiac Ring" + end + else + gear.ElementalCape.name = gear.default.obi_back + gear.ElementalRing.name = gear.default.obi_ring + end +end + + +-- Function to get the appropriate fast cast and/or recast staves for the current spell. +function set_elemental_staff(spell) + if spell.action_type ~= 'Magic' then + return + end + + gear.FastcastStaff.name = get_elemental_item_name("fastcast_staff", S{spell.element}) or gear.default.fastcast_staff or "" + gear.RecastStaff.name = get_elemental_item_name("recast_staff", S{spell.element}) or gear.default.recast_staff or "" +end + + +-- Gets the name of an elementally-aligned piece of gear within the player's +-- inventory that matches the conditions set in the parameters. +-- +-- item_type: Type of item as specified in the elemental_map mappings. +-- EG: gorget, belt, obi, fastcast_staff, recast_staff +-- +-- valid_elements: Elements that are valid for the action being taken. +-- IE: Weaponskill skillchain properties, or spell element. +-- +-- restricted_to_elements: Secondary elemental restriction that limits +-- whether the item check can be considered valid. +-- EG: Day or weather elements that have to match the spell element being queried. +-- +-- Returns: Nil if no match was found (either due to elemental restrictions, +-- or the gear isn't in the player inventory), or the name of the piece of +-- gear that matches the query. +function get_elemental_item_name(item_type, valid_elements, restricted_to_elements) + local potential_elements = restricted_to_elements or elements.list + local item_map = elements[item_type:lower()..'_of'] + + for element in (potential_elements.it or it)(potential_elements) do + if valid_elements:contains(element) and (player.inventory[item_map[element]] or player.wardrobe[item_map[element]] or player.wardrobe2[item_map[element]]) then + return item_map[element] + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Function to easily change to a given macro set or book. Book value is optional. +------------------------------------------------------------------------------------------------------------------- + +function set_macro_page(set,book) + if not tonumber(set) then + add_to_chat(123,'Error setting macro page: Set is not a valid number ('..tostring(set)..').') + return + end + if set < 1 or set > 10 then + add_to_chat(123,'Error setting macro page: Macro set ('..tostring(set)..') must be between 1 and 10.') + return + end + + if book then + if not tonumber(book) then + add_to_chat(123,'Error setting macro page: book is not a valid number ('..tostring(book)..').') + return + end + if book < 1 or book > 20 then + add_to_chat(123,'Error setting macro page: Macro book ('..tostring(book)..') must be between 1 and 20.') + return + end + send_command('@input /macro book '..tostring(book)..';wait 1.1;input /macro set '..tostring(set)) + else + send_command('@input /macro set '..tostring(set)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for including local user files. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to load user gear files in place of default gear sets. +-- Return true if one exists and was loaded. +function load_sidecar(job) + if not job then return false end + + -- filename format example for user-local files: whm_gear.lua, or playername_whm_gear.lua + local filenames = {player.name..'_'..job..'_gear.lua', job..'_gear.lua', + 'gear/'..player.name..'_'..job..'_gear.lua', 'gear/'..job..'_gear.lua', + 'gear/'..player.name..'_'..job..'.lua', 'gear/'..job..'.lua'} + return optional_include(filenames) +end + +-- Attempt to include user-globals. Return true if it exists and was loaded. +function load_user_globals() + local filenames = {player.name..'-globals.lua', 'user-globals.lua'} + return optional_include(filenames) +end + +-- Optional version of include(). If file does not exist, does not +-- attempt to load, and does not throw an error. +-- filenames takes an array of possible file names to include and checks +-- each one. +function optional_include(filenames) + for _,v in pairs(filenames) do + local path = gearswap.pathsearch({v}) + if path then + include(v) + return true + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for vars or other data manipulation. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to locate a specified name within the current alliance. +function find_player_in_alliance(name) + for party_index,ally_party in ipairs(alliance) do + for player_index,_player in ipairs(ally_party) do + if _player.name == name then + return _player + end + end + end +end + + +-- buff_set is a set of buffs in a library table (any of S{}, T{} or L{}). +-- This function checks if any of those buffs are present on the player. +function has_any_buff_of(buff_set) + return buff_set:any( + -- Returns true if any buff from buff set that is sent to this function returns true: + function (b) return buffactive[b] end + ) +end + + +-- Invert a table such that the keys are values and the values are keys. +-- Use this to look up the index value of a given entry. +function invert_table(t) + if t == nil then error('Attempting to invert table, received nil.', 2) end + + local i={} + for k,v in pairs(t) do + i[v] = k + end + return i +end + + +-- Gets sub-tables based on baseSet from the string str that may be in dot form +-- (eg: baseSet=sets, str='precast.FC', this returns the table sets.precast.FC). +function get_expanded_set(baseSet, str) + local cur = baseSet + for i in str:gmatch("[^.]+") do + if cur then + cur = cur[i] + end + end + + return cur +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions data and event tracking. +------------------------------------------------------------------------------------------------------------------- + +-- This is a function that can be attached to a registered event for 'time change'. +-- It will send a call to the update() function if the time period changes. +-- It will also call job_time_change when any of the specific time class values have changed. +-- To activate this in your job lua, add this line to your user_setup function: +-- windower.register_event('time change', time_change) +-- +-- Variables it sets: classes.Daytime, and classes.DuskToDawn. They are set to true +-- if their respective descriptors are true, or false otherwise. +function time_change(new_time, old_time) + local was_daytime = classes.Daytime + local was_dusktime = classes.DuskToDawn + + if new_time >= 6*60 and new_time < 18*60 then + classes.Daytime = true + else + classes.Daytime = false + end + + if new_time >= 17*60 or new_time < 7*60 then + classes.DuskToDawn = true + else + classes.DuskToDawn = false + end + + if was_daytime ~= classes.Daytime or was_dusktime ~= classes.DuskToDawn then + if job_time_change then + job_time_change(new_time, old_time) + end + + handle_update({'auto'}) + end +end + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-documentation.txt b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-documentation.txt new file mode 100644 index 0000000..8f17241 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/Mote-documentation.txt @@ -0,0 +1 @@ +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation on the usage of these include files.
\ No newline at end of file diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/closetCleaner.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/closetCleaner.lua new file mode 100644 index 0000000..50a35ef --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/closetCleaner.lua @@ -0,0 +1,377 @@ +--Copyright © 2016-2017, Brimstone +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of closetCleaner nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL Brimstone BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-- _addon.version = '1.0' + +local cc = {} +config = require ('config') +cc.sandbox = {} +cc.sandbox.windower = setmetatable({}, {__index = windower}) +cc.sandbox.windower.coroutine = functions.empty +cc.sandbox.windower.register_event = functions.empty +cc.sandbox.windower.raw_register_event = functions.empty +cc.sandbox.windower.register_unhandled_command = functions.empty + +defaults = T{} +-- Jobs you want to execute with, recomment put all active jobs you have lua for will look for <job>.lua or <playername>_<job>.lua files +defaults.ccjobs = { 'BLM', 'BLU', 'BRD', 'BST', 'COR', 'DNC', 'DRG', 'DRK', 'GEO', 'MNK', 'NIN', 'PLD', 'PUP', 'RDM', 'RNG', 'RUN', 'SAM', 'SCH', 'SMN', 'THF', 'WAR', 'WHM' } +-- Put any items in your inventory here you don't want to show up in the final report +-- recommended for furniture, food, meds, pop items or any gear you know you want to keep for some reason +-- use * for anything. +defaults.ccignore = S{ "Rem's Tale*", "Storage Slip *" } +-- Set to nil or delete for unlimited +defaults.ccmaxuse = nil +-- List bags you want to not check against, needs to match "Location" column in <player>_report.txt +defaults.ccskipBags = S{ 'Storage', 'Temporary' } +-- this prints out the _sets _ignored and _inventory files +ccDebug = false + +settings = config.load('ccConfig.xml',defaults) + +register_unhandled_command(function(command) + command = command and command:lower() or nil + if command ~= 'cc' and command ~= 'closetcleaner' then + return + end + setmetatable(cc.sandbox, {__index = gearswap.user_env}) + cc.sandbox.itemsBylongName = T{} + cc.sandbox.itemsByName = T{} + cc.sandbox.inventoryGear = T{} + cc.sandbox.gsGear = T{} + for k,v in pairs(gearswap.res.items) do + cc.sandbox.itemsBylongName[gearswap.res.items[k].name_log:lower()] = k + cc.sandbox.itemsByName[gearswap.res.items[k].name:lower()] = k + end + cc.sandbox.jobs = {} + for k,v in pairs(gearswap.res.jobs) do + cc.sandbox.jobs[gearswap.res.jobs[k].english_short] = k + end + if not windower.dir_exists(windower.addon_path..'report') then + windower.create_dir(windower.addon_path..'report') + end + local path = windower.addon_path:gsub('\\','/') + path = path..'report/'..player.name + cc.run_report(path) + cc.sandbox = {} + cc.sandbox.windower = setmetatable({}, {__index = windower}) + cc.sandbox.windower.register_event = functions.empty + cc.sandbox.windower.raw_register_event = functions.empty + cc.sandbox.windower.register_unhandled_command = functions.empty + return true +end) + +-- This function creates the report and generates the calls to the other functions +function cc.run_report(path) + mainReportName = path..'_report.txt' + local f = io.open(mainReportName,'w+') + f:write('closetCleaner Report:\n') + f:write('=====================\n\n') + cc.export_inv(path) + cc.export_sets(path) + for k,v in pairs(cc.sandbox.inventoryGear) do + if cc.sandbox.gsGear[k] == nil then + cc.sandbox.gsGear[k] = 0 + end + end + data = T{"Name", " | ", "Count", " | ", "Location", " | ", "Jobs Used", " | ", "Long Name"} + form = T{"%25s", "%3s", "%10s", "%3s", "%20s", "%3s", "%-88s", "%3s", "%60s"} + cc.print_row(f, data, form) + cc.print_break(f, form) + if ccDebug then + ignoredReportName = path..'_ignored.txt' + f2 = io.open(ignoredReportName,'w+') + f2:write('closetCleaner ignored Report:\n') + f2:write('=====================\n\n') + cc.print_row(f2, data, form) + cc.print_break(f2, form) + end + for k,v in cc.spairs(cc.sandbox.gsGear, function(t,a,b) return t[b] > t[a] end) do + if settings.ccmaxuse == nil or v <= settings.ccmaxuse then + printthis = 1 + if not cc.job_used[k] then + cc.job_used[k] = " " + end + for s in pairs(settings.ccignore) do + if windower.wc_match(gearswap.res.items[k].english, s) then + printthis = nil + if cc.sandbox.inventoryGear[k] == nil then + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", "NOT FOUND", " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + else + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.sandbox.inventoryGear[k], " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + end + if ccDebug then + cc.print_row(f2, data, form) + end + break + end + end + if printthis then + if cc.sandbox.inventoryGear[k] == nil then + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", "NOT FOUND", " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + else + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.sandbox.inventoryGear[k], " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + end + cc.print_row(f, data, form) + end + end + end + if ccDebug then + f2:close() + print("File created: "..ignoredReportName) + end + f:close() + print("File created: "..mainReportName) +end + + -- This function tallies all the gear in your inventory +function cc.export_inv(path) + if ccDebug then + reportName = path..'_inventory.txt' + finv = io.open(reportName,'w+') + finv:write('closetCleaner Inventory Report:\n') + finv:write('=====================\n\n') + end + + local item_list = T{} + checkbag = true + for n = 0, #gearswap.res.bags do + if not settings.ccskipBags:contains(gearswap.res.bags[n].english) then + for i,v in ipairs(gearswap.get_item_list(gearswap.items[gearswap.res.bags[n].english:gsub(' ', ''):lower()])) do + if v.name ~= empty then + local slot = gearswap.xmlify(tostring(v.slot)) + local name = gearswap.xmlify(tostring(v.name)):gsub('NUM1','1') + + if cc.sandbox.itemsByName[name:lower()] ~= nil then + itemid = cc.sandbox.itemsByName[name:lower()] + elseif cc.sandbox.itemsBylongName[name:lower()] ~= nil then + itemid = cc.sandbox.itemsBylongName[name:lower()] + else + print("Item: "..name.." not found in gearswap.resources!") + end + if ccDebug then + finv:write("Name: "..name.." Slot: "..slot.." Bag: "..gearswap.res.bags[n].english.."\n") + end + if cc.sandbox.inventoryGear[itemid] == nil then + cc.sandbox.inventoryGear[itemid] = gearswap.res.bags[n].english + else + cc.sandbox.inventoryGear[itemid] = cc.sandbox.inventoryGear[itemid]..", "..gearswap.res.bags[n].english + end + end + end + end + end + if ccDebug then + finv:close() + print("File created: "..reportName) + end +end + +-- loads all the relevant jobs.lua files and inserts the sets tables into a supersets table: +-- supersets.<JOB>.sets.... +function cc.export_sets(path) + if ccDebug then + reportName = path..'_sets.txt' + fsets = io.open(reportName,'w+') + fsets:write('closetCleaner sets Report:\n') + fsets:write('=====================\n\n') + end + cc.supersets = {} + cc.job_used = T{} + cc.job_logged = T() + fpath = windower.addon_path:gsub('\\','/') + fpath = fpath:gsub('//','/') + fpath = string.lower(fpath) + dpath = fpath..'data/' + for i,v in ipairs(settings.ccjobs) do + dname = string.lower(dpath..player.name..'/'..v..'.lua') + lname = string.lower(dpath..player.name..'_'..v..'.lua') + lgname = string.lower(dpath..player.name..'_'..v..'_gear.lua') + sname = string.lower(dpath..v..'.lua') + sgname = string.lower(dpath..v..'_gear.lua') + if windower.file_exists(lgname) then + cc.supersets[v] = cc.extract_sets(lgname) + elseif windower.file_exists(lname) then + cc.supersets[v] = cc.extract_sets(lname) + elseif windower.file_exists(sgname) then + cc.supersets[v] = cc.extract_sets(sgname) + elseif windower.file_exists(sname) then + cc.supersets[v] = cc.extract_sets(sname) + elseif windower.file_exists(dname) then + cc.supersets[v] = cc.extract_sets(dname) + else + print('lua file for '..v..' not found!') + end + end + cc.list_sets(cc.supersets, fsets) + cc.supersets = nil + if ccDebug then + fsets:close() + print("File created: "..reportName) + end +end + +-- sets the 'sets' and puts them into supersets based off file name. +function cc.extract_sets(file) + local user_file = gearswap.loadfile(file) + if user_file then + gearswap.setfenv(user_file, cc.sandbox) + cc.sandbox.sets = {} + user_file() + local def_gear = cc.sandbox.init_get_sets or cc.sandbox.get_sets + if def_gear then + def_gear() + end + return table.copy(cc.sandbox.sets) + else + print('lua file for '..file..' not found!') + end +end + +-- this function tallies the items used in each lua file +function cc.list_sets(t, f) + write_sets = T{} + local print_r_cache={} + local function sub_print_r(t,fromTab) + if (type(t)=="table") then + for pos,val in pairs(t) do + if S{"WAR", "MNK", "WHM", "BLM", "RDM", "THF", "PLD", "DRK", "BST", "BRD", "RNG", "SAM", "NIN", "DRG", "SMN", "BLU", "COR", "PUP", "DNC", "SCH", "GEO", "RUN"}:contains(pos) then + job = pos + end + if (type(val)=="table") then + sub_print_r(val,job) + elseif (type(val)=="string") then + if val ~= "" and val ~= "empty" then + if S{"name", "main", "sub", "range", "ammo", "head", "neck", "left_ear", "right_ear", "body", "hands", "left_ring", "right_ring", "back", "waist", "legs", "feet", "ear1", "ear2", "ring1", "ring2", "lear", "rear", "lring", "rring"}:contains(pos) then + if cc.sandbox.itemsByName[val:lower()] ~= nil then + itemid = cc.sandbox.itemsByName[val:lower()] + elseif cc.sandbox.itemsBylongName[val:lower()] ~= nil then + itemid = cc.sandbox.itemsBylongName[val:lower()] + else + print("Item: '"..val.."' not found in gearswap.resources! "..pos) + end + if ccDebug then + f:write('Processing '..job..' name for val '..val..' id '..itemid..'\n') + end + if write_sets[itemid] == nil then + write_sets[itemid] = 1 + if cc.job_used[itemid] == nil then + cc.job_used[itemid] = job + cc.job_logged[itemid..job] = 1 + else + cc.job_used[itemid] = cc.job_used[itemid]..","..job + cc.job_logged[itemid..job] = 1 + end + else + write_sets[itemid] = write_sets[itemid] + 1 + if cc.job_logged[itemid..job] == nil then + cc.job_used[itemid] = cc.job_used[itemid]..","..job + cc.job_logged[itemid..job] = 1 + end + end + end + end + elseif (type(val)=="number") then + print("Found Number: "..val.." from "..pos.." table "..t) + else + print("Error: Val needs to be table or string "..type(val)) + end + end + end + end + sub_print_r(t,nil) + if ccDebug then + data = T{"Name", " | ", "Count", " | ", "Jobs", " | ", "Long Name"} + form = T{"%22s", "%3s", "%10s", "%3s", "%88s", "%3s", "%60s"} + cc.print_row(f, data, form) + cc.print_break(f, form) + f:write('\n') + for k,v in pairs(write_sets) do + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + cc.print_row(f, data, form) + cc.sandbox.gsGear[k] = v + end + f:write() + else + for k,v in pairs(write_sets) do + cc.sandbox.gsGear[k] = v + end + end +end + +-- interate throught table in a sorted order. +function cc.spairs(t, order) + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys+1] = k end + + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a,b) return order(t, a, b) end) + else + table.sort(keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +-- pass in file handle and a table of formats and table of data +function cc.print_row(f, data, form) + for k,v in pairs(data) do + f:write(string.format(form[k], v)) + end + f:write('\n') +end + +-- pass in file handle and a table of formats and table of data +function cc.print_break(f, form) + for k,v in pairs(form) do + number = string.match(v,"%d+") + for i=1,number do + f:write('-') + end + -- f:write(' ') -- can add characters to end here like spaces but subtract from number in the for loop above + end + f:write('\n') +end + + +function cc.include(str) + str = str:lower() + if not (str == 'closetcleaner' or str == 'closetcleaner.lua') then + include(str, cc.sandbox) + end +end + +cc.sandbox.include = cc.include +cc.sandbox.require = cc.include
\ No newline at end of file diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/organizer-lib.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/organizer-lib.lua new file mode 100644 index 0000000..c3111f6 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/organizer-lib.lua @@ -0,0 +1,237 @@ +-- Organizer library v2 + +local org = {} +register_unhandled_command(function(...) + local cmds = {...} + for _,v in ipairs(cmds) do + if S{'organizer','organize','org','o'}:contains(v:lower()) then + org.export_set() + return true + end + end + return false +end) + + +function org.export_set() + if not sets then + windower.add_to_chat(123,'Organizer Library: Cannot export your sets for collection because the table is nil.') + return + elseif not windower.dir_exists(windower.windower_path..'addons/organizer/') then + windower.add_to_chat(123,'Organizer Library: The organizer addon is not installed. Activate it in the launcher.') + return + end + + -- Makes a big table keyed to item resource tables, with values that are 1-based + -- numerically indexed tables of different entries for each of the items from the sets table. + local item_list = org.unpack_names({},'L1',sets,{}) + + local trans_item_list = org.identify_items(item_list) + + for i,v in pairs(trans_item_list) do + trans_item_list[i] = org.simplify_entry(v) + end + + if trans_item_list:length() == 0 then + windower.add_to_chat(123,'Organizer Library: Your sets table is empty.') + return + end + + local flattab = T{} + for name,tab in pairs(trans_item_list) do + for _,info in ipairs(tab) do + flattab:append({id=tab.id,name=tab.name,log_name=tab.log_name,augments=info.augments,count=info.count}) + end + end + + -- See if we have any non-equipment items to drag along + if organizer_items then + local organizer_item_list = org.unpack_names({}, 'L1', organizer_items, {}) + + for _,tab in pairs(org.identify_items(organizer_item_list)) do + count = gearswap.res.items[tab.id].stack + flattab:append({id=tab.id,name=tab.name,log_name=tab.log_name,count=count}) + end + end + + -- At this point I have a table of equipment pieces indexed by the inventory name. + -- I need to make a function that will translate that into a list of pieces in + -- inventory or wardrobe. + -- #trans_item_list[i] = Number of a given item + -- trans_item_list[i].id = item ID + + local ward_ids = {8,10,11,12} + local wardrobes = {} + local ward = {} + + for _,id in pairs(ward_ids) do + wardrobes[id] = windower.ffxi.get_items(id) + wardrobes[id].max = windower.ffxi.get_bag_info(id).max + ward[id] = T{} + end + + local inv = T{} + for i,v in ipairs(flattab) do + local found + local ward_id + -- Iterate over the wardrobes and look for gear from the list that is already in wardrobes, then eliminate it from the list + for id,wardrobe in pairs(wardrobes) do + for n,m in ipairs(wardrobe) do + if m.id == v.id and (not v.augments or v.augments and gearswap.extdata.decode(m).augments and gearswap.extdata.compare_augments(v.augments,gearswap.extdata.decode(m).augments)) then + found = n + break + end + end + if found then + ward_id = id + break + end + end + if found then + table.remove(wardrobes[ward_id],found) + ward[ward_id]:append(v) + else + inv:append(v) + end + end + + local inventory_max = windower.ffxi.get_bag_info(0).max + + for id=1,4 do + if #inv > inventory_max and #ward[id] + (#inv-inventory_max) < wardrobes[id].max then + local available = wardrobes[id].max - #ward[id] + local length = math.min(#inv-80,available) + ward:extend(inv:slice(1-length)) + end + end + + if #inv > inventory_max then + windower.add_to_chat(123,'Organizer Library: Your sets table contains too many items.') + return + end + + -- Scan wardrobe, eliminate items from your table that are in wardrobe + -- Scan inventory + + local fi = file.new('../organizer/data/inventory/organizer-lib-file.lua') + fi:write('-- Generated by the Organizer Library ('..os.date()..')\nreturn '..(inv:tovstring({'augments','log_name','name','id','count'}))) + + for _,id in ipairs(ward_ids) do + local fw = file.new('../organizer/data/'..gearswap.res.bags[id].command..'/organizer-lib-file.lua') + fw:write('-- Generated by the Organizer Library ('..os.date()..')\nreturn '..(ward[id]:tovstring({'augments','log_name','name','id','count'}))) + end + + windower.send_command('wait 0.5;org o organizer-lib-file') +end + +function org.simplify_entry(tab) + -- Some degree of this needs to be done in unpack_names or I won't be able to detect when two identical augmented items are equipped. + local output = T{id=tab.id,name=tab.name,log_name=tab.log_name} + local rare = gearswap.res.items[tab.id].flags:contains('Rare') + for i,v in ipairs(tab) do + local handled = false + if v.augment then + v.augments = {v.augment} + v.augment = nil + end + + for n,m in ipairs(output) do + if (not v.bag or v.bag and v.bag == m.bag) and v.slot == m.slot and + (not v.augments or ( m.augments and gearswap.extdata.compare_augments(v.augments,m.augments))) then + output[n].count = math.min(math.max(output[n].count,v.count),gearswap.res.items[tab.id].stack) + handled = true + break + elseif (not v.bag or v.bag and v.bag == m.bag) and v.slot == m.slot and v.augments and not m.augments then + -- v has augments, but there currently exists a matching version of the + -- item without augments in the output table. Replace the entry with the augmented entry + local countmax = math.min(math.max(output[n].count,v.count),gearswap.res.items[tab.id].stack) + output[n] = v + output[n].count = countmax + handled = true + break + elseif rare then + handled = true + break + end + end + if not handled then + output:append(v) + end + + end + return output +end + +function org.identify_items(tab) + local name_to_id_map = {} + local items = windower.ffxi.get_items() + for id,inv in pairs(items) do + if type(inv) == 'table' then + for ind,item in ipairs(inv) do + if type(item) == 'table' and item.id and item.id ~= 0 then + name_to_id_map[gearswap.res.items[item.id][gearswap.language]:lower()] = item.id + name_to_id_map[gearswap.res.items[item.id][gearswap.language..'_log']:lower()] = item.id + end + end + end + end + local trans = T{} + for i,v in pairs(tab) do + local item = name_to_id_map[i:lower()] and table.reassign({},gearswap.res.items[name_to_id_map[i:lower()]]) --and org.identify_unpacked_name(i,name_to_id_map) + if item then + local n = gearswap.res.items[item.id][gearswap.language]:lower() + local ln = gearswap.res.items[item.id][gearswap.language..'_log']:lower() + if not trans[n] then + trans[n] = T{id=item.id, + name=n, + log_name=ln, + } + end + trans[n]:extend(v) + end + end + return trans +end + +function org.unpack_names(ret_tab,up,tab_level,unpacked_table) + for i,v in pairs(tab_level) do + local flag = false + if type(v)=='table' and i ~= 'augments' and not ret_tab[tostring(tab_level[i])] then + ret_tab[tostring(tab_level[i])] = true + unpacked_table, ret_tab = org.unpack_names(ret_tab,i,v,unpacked_table) + elseif i=='name' then + -- v is supposed to be a name, then. + flag = true + elseif type(v) == 'string' and v~='augment' and v~= 'augments' and v~= 'priority' then + -- v is a string that's not any known option of gearswap, so treat it as an item name. + -- I really need to make a set of the known advanced table options and use that instead. + flag = true + end + if flag then + local n = tostring(v):lower() + if not unpacked_table[n] then unpacked_table[n] = {} end + local ind = #unpacked_table[n] + 1 + if i == 'name' and gearswap.slot_map[tostring(up):lower()] then -- Advanced Table + unpacked_table[n][ind] = tab_level + unpacked_table[n][ind].count = unpacked_table[n][ind].count or 1 + unpacked_table[n][ind].slot = gearswap.slot_map[up:lower()] + elseif gearswap.slot_map[tostring(i):lower()] then + unpacked_table[n][ind] = {slot=gearswap.slot_map[i:lower()],count=1} + end + end + end + return unpacked_table, ret_tab +end + +function org.string_augments(tab) + local aug_str = '' + if tab.augments then + for aug_ind,augment in pairs(tab.augments) do + if augment ~= 'none' then aug_str = aug_str..'['..aug_ind..'] = '..'"'..augment..'",\n' end + end + end + if tab.augment then + if tab.augment ~= 'none' then aug_str = aug_str.."'"..augment.."'," end + end + if aug_str ~= '' then return '{\n'..aug_str..'}' end +end
\ No newline at end of file diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Modes.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Modes.lua new file mode 100644 index 0000000..35036b3 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Modes.lua @@ -0,0 +1,358 @@ +------------------------------------------------------------------------------------------------------------------- +-- This include library allows use of specially-designed tables for tracking +-- certain types of modes and state. +-- +-- Usage: include('Modes.lua') +-- +-- Construction syntax: +-- +-- 1) Create a new list of mode values. The first value will always be the default. +-- MeleeMode = M{'Normal', 'Acc', 'Att'} -- Construct as a basic table, using braces. +-- MeleeMode = M('Normal', 'Acc', 'Att') -- Pass in a list of strings, using parentheses. +-- +-- Optional: If constructed as a basic table, and the table contains a key value +-- of 'description', that description will be saved for future reference. +-- If a simple list of strings is passed in, no description will be set. +-- +-- 2) Create a boolean mode with a specified default value (note parentheses): +-- UseLuzafRing = M(true) +-- UseLuzafRing = M(false) +-- +-- Optional: A string may be provided that will be used as the mode description: +-- UseLuzafRing = M(false, 'description') +-- UseLuzafRing = M(true, 'description') +-- +-- +-- Public information fields (all are case-insensitive): +-- +-- 1) m.description -- Get a text description of the mode table, if it's been set. +-- 2) m.current or m.value -- Gets the current mode value. Booleans will return the strings "on" or "off". +-- 3) m.index -- Gets the current index value, or true/false for booleans. +-- +-- +-- Public class functions: +-- +-- 1) m:describe(str) -- Sets the description for the mode table to the provided string value. +-- 2) m:options(options) -- Redefine the options for a list mode table. Cannot be used on a boolean table. +-- +-- +-- Public mode manipulation functions: +-- +-- 1) m:cycle() -- Cycles through the list going forwards. Acts as a toggle on boolean mode vars. +-- 2) m:cycleback() -- Cycles through the list going backwards. Acts as a toggle on boolean mode vars. +-- 3) m:toggle() -- Toggles a boolean Mode between true and false. +-- 4) m:set(n) -- Sets the current mode value to n. +-- Note: If m is boolean, n can be boolean true/false, or string of on/off/true/false. +-- Note: If m is boolean and no n is given, this forces m to true. +-- 5) m:unset() -- Sets a boolean mode var to false. +-- 6) m:reset() -- Returns the mode var to its default state. +-- 7) m:default() -- Same as reset() +-- +-- All public functions return the current value after completion. +-- +-- +-- Example usage: +-- +-- sets.MeleeMode.Normal = {} +-- sets.MeleeMode.Acc = {} +-- sets.MeleeMode.Att = {} +-- +-- MeleeMode = M{'Normal', 'Acc', 'Att', ['description']='Melee Mode'} +-- MeleeMode:cycle() +-- equip(sets.engaged[MeleeMode.current]) +-- MeleeMode:options('Normal', 'LowAcc', 'HighAcc') +-- >> Changes the option list, but the description stays 'Melee Mode' +-- +-- +-- sets.LuzafRing.on = {ring2="Luzaf's Ring"} +-- sets.LuzafRing.off = {} +-- +-- UseLuzafRing = M(false) +-- UseLuzafRing:toggle() +-- equip(sets.precast['Phantom Roll'], sets.LuzafRing[UseLuzafRing.value]) +------------------------------------------------------------------------------------------------------------------- + + +_meta = _meta or {} +_meta.M = {} +_meta.M.__class = 'mode' +_meta.M.__methods = {} + + +-- Default constructor for mode tables +-- M{'a', 'b', etc, ['description']='a'} -- defines a mode list, description 'a' +-- M('a', 'b', etc) -- defines a mode list, no description +-- M('a') -- defines a mode list, default 'a' +-- M{['description']='a'} -- defines a mode list, default 'Normal', description 'a' +-- M{} -- defines a mode list, default 'Normal', no description +-- M(false) -- defines a mode boolean, default false, no description +-- M(true) -- defines a mode boolean, default true, no description +-- M(false, 'a') -- defines a mode boolean, default false, description 'a' +-- M(true, 'a') -- defines a mode boolean, default true, description 'a' +-- M() -- defines a mode boolean, default false, no description +function M(t, ...) + local m = {} + m._track = {} + + -- If we're passed a list of strings, convert them to a table + local args = {...} + if type(t) == 'string' then + t = {[1] = t} + + for ind, val in ipairs(args) do + t[ind+1] = val + end + end + + -- Construct the table that we'll be added the metadata to + if type(t) == 'table' then + m._track._type = 'list' + m._track._invert = {} + m._track._count = 0 + + if t['description'] then + m._track._description = t['description'] + end + + -- Only copy numerically indexed values + for ind, val in ipairs(t) do + m[ind] = val + m._track._invert[val] = ind + m._track._count = ind + end + + if m._track._count == 0 then + m[1] = 'Normal' + m._track._invert['Normal'] = 1 + m._track._count = 1 + end + + m._track._default = 1 + elseif type(t) == 'boolean' or t == nil then + m._track._type = 'boolean' + m._track._default = t or false + m._track._description = args[1] + m._track._count = 2 + -- Text lookups for bool values + m[true] = 'on' + m[false] = 'off' + else + -- Construction failure + error("Unable to construct a mode table with the provided parameters.", 2) + end + + m._track._current = m._track._default + + return setmetatable(m, _meta.M) +end + +-------------------------------------------------------------------------- +-- Metamethods +-- Functions that will be used as metamethods for the class +-------------------------------------------------------------------------- + +-- Handler for __index when trying to access the current mode value. +-- Handles indexing 'current' or 'value' keys. +_meta.M.__index = function(m, k) + if type(k) == 'string' then + local lk = k:lower() + if lk == 'current' or lk == 'value' then + return m[m._track._current] + elseif lk == 'index' then + return m._track._current + elseif m._track['_'..lk] then + return m._track['_'..lk] + else + return _meta.M.__methods[lk] + end + end +end + +-- Tostring handler for printing out the table and its current state. +_meta.M.__tostring = function(m) + local res = '' + if m._track._description then + res = res .. m._track._description .. ': ' + end + + if m._track._type == 'list' then + res = res .. '{' + for k,v in ipairs(m) do + res = res..tostring(v) + if m[k+1] ~= nil then + res = res..', ' + end + end + res = res..'}' + else + res = res .. 'Boolean' + end + + res = res .. ' ('..tostring(m.Current).. ')' + + -- Debug addition + --res = res .. ' [' .. m._track._type .. '/' .. tostring(m._track._current) .. ']' + + return res +end + +-- Length handler for the # value lookup. +_meta.M.__len = function(m) + return m._track._count +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that can be used as public methods for manipulating the class. +-------------------------------------------------------------------------- + +-- Function to set the table's description. +_meta.M.__methods['describe'] = function(m, str) + if type(str) == 'string' then + m._track._description = str + else + error("Invalid argument type: " .. type(str), 2) + end +end + +-- Function to change the list of options available. +-- Leaves the description intact. +-- Cannot be used on boolean classes. +_meta.M.__methods['options'] = function(m, ...) + if m._track._type == 'boolean' then + error("Cannot revise the options list for a boolean mode class.", 2) + end + + local options = {...} + -- Always include a default option if nothing else is given. + if #options == 0 then + options = {'Normal'} + end + + -- Zero-out existing values and clear the tracked inverted list + -- and member count. + for key,val in ipairs(m) do + m[key] = nil + end + m._track._invert = {} + m._track._count = 0 + + -- Insert in new data. + for key,val in ipairs(options) do + m[key] = val + m._track._invert[val] = key + m._track._count = key + end + + m._track._current = m._track._default +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that will be used as public methods for manipulating state. +-------------------------------------------------------------------------- + +-- Cycle forwards through the list +_meta.M.__methods['cycle'] = function(m) + if m._track._type == 'list' then + m._track._current = (m._track._current % m._track._count) + 1 + else + m:toggle() + end + + return m.Current +end + +-- Cycle backwards through the list +_meta.M.__methods['cycleback'] = function(m) + if m._track._type == 'list' then + m._track._current = m._track._current - 1 + if m._track._current < 1 then + m._track._current = m._track._count + end + else + m:toggle() + end + + return m.Current +end + +-- Toggle a boolean value +_meta.M.__methods['toggle'] = function(m) + if m._track._type == 'boolean' then + m._track._current = not m._track._current + else + error("Cannot toggle a list mode.", 2) + end + + return m.Current +end + + +-- Set the current value +_meta.M.__methods['set'] = function(m, val) + if m._track._type == 'boolean' then + if val == nil then + m._track._current = true + elseif type(val) == 'boolean' then + m._track._current = val + elseif type(val) == 'string' then + val = val:lower() + if val == 'on' or val == 'true' then + m._track._current = true + elseif val == 'off' or val == 'false' then + m._track._current = false + else + error("Unrecognized value: "..tostring(val), 2) + end + else + error("Unrecognized value type: "..type(val), 2) + end + else + if m._track._invert[val] then + m._track._current = m._track._invert[val] + else + local found = false + for v, ind in pairs(m._track._invert) do + if val:lower() == v:lower() then + m._track._current = ind + found = true + break + end + end + + if not found then + error("Unknown mode value: " .. tostring(val), 2) + end + end + end + + return m.Current +end + +-- Reset to the default value +_meta.M.__methods['default'] = function(m) + m._track._current = m._track._default + + return m.Current +end + +-- Reset to the default value +_meta.M.__methods['reset'] = function(m) + m._track._current = m._track._default + + return m.Current +end + +-- Forces a boolean mode to false +_meta.M.__methods['unset'] = function(m) + if m._track._type == 'boolean' then + m._track._current = false + else + error("Cannot unset a list mode class.", 2) + end + + return m.Current +end diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Globals.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Globals.lua new file mode 100644 index 0000000..8546c39 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Globals.lua @@ -0,0 +1,112 @@ +------------------------------------------------------------------------------------------------------------------- +-- Tables and functions for commonly-referenced gear that job files may need, but +-- doesn't belong in the global Mote-Include file since they'd get clobbered on each +-- update. +-- Creates the 'gear' table for reference in other files. +-- +-- Note: Function and table definitions should be added to user, but references to +-- the contained tables via functions (such as for the obi function, below) use only +-- the 'gear' table. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Modify the sets table. Any gear sets that are added to the sets table need to +-- be defined within this function, because sets isn't available until after the +-- include is complete. It is called at the end of basic initialization in Mote-Include. +------------------------------------------------------------------------------------------------------------------- + +function define_global_sets() + -- Special gear info that may be useful across jobs. + + -- Staffs + gear.Staff = {} + gear.Staff.HMP = 'Chatoyant Staff' + gear.Staff.PDT = 'Earth Staff' + + -- Dark Rings + gear.DarkRing = {} + gear.DarkRing.physical = {name="Dark Ring",augments={'Magic dmg. taken -3%','Spell interruption rate down -5%','Phys. dmg. taken -6%'}} + gear.DarkRing.magical = {name="Dark Ring", augments={'Magic dmg. taken -6%','Breath dmg. taken -5%'}} + + -- Default items for utility gear values. + gear.default.weaponskill_neck = "Asperity Necklace" + gear.default.weaponskill_waist = "Caudata Belt" + gear.default.obi_waist = "Cognition Belt" + gear.default.obi_back = "Toro Cape" + gear.default.obi_ring = "Strendu Ring" + gear.default.fastcast_staff = "" + gear.default.recast_staff = "" +end + +------------------------------------------------------------------------------------------------------------------- +-- Functions to set user-specified binds, generally on load and unload. +-- Kept separate from the main include so as to not get clobbered when the main is updated. +------------------------------------------------------------------------------------------------------------------- + +-- Function to bind GearSwap binds when loading a GS script. +function global_on_load() + send_command('bind f9 gs c cycle OffenseMode') + send_command('bind ^f9 gs c cycle DefenseMode') + send_command('bind !f9 gs c cycle WeaponskillMode') + send_command('bind f10 gs c activate PhysicalDefense') + send_command('bind ^f10 gs c cycle PhysicalDefenseMode') + send_command('bind !f10 gs c toggle kiting') + send_command('bind f11 gs c activate MagicalDefense') + send_command('bind ^f11 gs c cycle CastingMode') + send_command('bind f12 gs c update user') + send_command('bind ^f12 gs c cycle IdleMode') + send_command('bind !f12 gs c reset defense') + + send_command('bind ^- gs c toggle selectnpctargets') + send_command('bind ^= gs c cycle pctargetmode') +end + +-- Function to revert binds when unloading. +function global_on_unload() + send_command('unbind f9') + send_command('unbind ^f9') + send_command('unbind !f9') + send_command('unbind f10') + send_command('unbind ^f10') + send_command('unbind !f10') + send_command('unbind f11') + send_command('unbind ^f11') + send_command('unbind !f11') + send_command('unbind f12') + send_command('unbind ^f12') + send_command('unbind !f12') + + send_command('unbind ^-') + send_command('unbind ^=') +end + +------------------------------------------------------------------------------------------------------------------- +-- Global event-handling functions. +------------------------------------------------------------------------------------------------------------------- + +-- Global intercept on precast. +function user_precast(spell, action, spellMap, eventArgs) + cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + refine_waltz(spell, action, spellMap, eventArgs) +end + +-- Global intercept on midcast. +function user_midcast(spell, action, spellMap, eventArgs) + -- Default base equipment layer of fast recast. + if spell.action_type == 'Magic' and sets.midcast and sets.midcast.FastRecast then + equip(sets.midcast.FastRecast) + end +end + +-- Global intercept on buff change. +function user_buff_change(buff, gain, eventArgs) + -- Create a timer when we gain weakness. Remove it when weakness is gone. + if buff:lower() == 'weakness' then + if gain then + send_command('timers create "Weakness" 300 up abilities/00255.png') + else + send_command('timers delete "Weakness"') + end + end +end + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Include.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Include.lua new file mode 100644 index 0000000..6ab1614 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Include.lua @@ -0,0 +1,1125 @@ +------------------------------------------------------------------------------------------------------------------- +-- Common variables and functions to be included in job scripts, for general default handling. +-- +-- Include this file in the get_sets() function with the command: +-- include('Mote-Include.lua') +-- +-- It will then automatically run its own init_include() function. +-- +-- IMPORTANT: This include requires supporting include files: +-- Mote-Utility +-- Mote-Mappings +-- Mote-SelfCommands +-- Mote-Globals +-- +-- Place the include() directive at the start of a job's get_sets() function. +-- +-- Included variables and functions are considered to be at the same scope level as +-- the job script itself, and can be used as such. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Initialization function that defines variables to be used. +-- These are accessible at the including job lua script's scope. +-- +-- Auto-initialize after defining this function. +------------------------------------------------------------------------------------------------------------------- + + +function init_include() + -- Used to define various types of data mappings. These may be used in the initialization, + -- so load it up front. + include('Mote-Mappings') + + -- Var for tracking misc info + info = {} + + -- Var for tracking state values + state = {} + + -- General melee offense/defense modes, allowing for hybrid set builds, as well as idle/resting/weaponskill. + state.OffenseMode = 'Normal' + state.DefenseMode = 'Normal' + state.RangedMode = 'Normal' + state.WeaponskillMode = 'Normal' + state.CastingMode = 'Normal' + state.IdleMode = 'Normal' + state.RestingMode = 'Normal' + + -- All-out defense state, either physical or magical + state.Defense = {} + state.Defense.Active = false + state.Defense.Type = 'Physical' + state.Defense.PhysicalMode = 'PDT' + state.Defense.MagicalMode = 'MDT' + + state.Kiting = false + state.MaxWeaponskillDistance = 0 + + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + + state.CombatWeapon = nil + state.CombatForm = nil + + state.Buff = {} + + + -- Vars for specifying valid mode values. + -- Defaults here are just for example. Set them properly in each job file. + options = {} + options.OffenseModes = {'Normal'} + options.DefenseModes = {'Normal'} + options.RangedModes = {'Normal'} + options.WeaponskillModes = {'Normal'} + options.CastingModes = {'Normal'} + options.IdleModes = {'Normal'} + options.RestingModes = {'Normal'} + options.PhysicalDefenseModes = {'PDT'} + options.MagicalDefenseModes = {'MDT'} + + options.TargetModes = {'default', 'stpc', 'stpt', 'stal'} + + + -- Spell mappings to describe a 'type' of spell. Used when searching for valid sets. + classes = {} + -- Basic spell mappings are based on common spell series. + -- EG: 'Cure' for Cure, Cure II, Cure III, Cure IV, Cure V, or Cure VI. + classes.SpellMaps = spell_maps + -- List of spells and spell maps that don't benefit from greater skill (though + -- they may benefit from spell-specific augments, such as improved regen or refresh). + -- Spells that fall under this category will be skipped when searching for + -- spell.skill sets. + classes.NoSkillSpells = no_skill_spells_list + classes.SkipSkillCheck = false + -- Custom, job-defined class, like the generic spell mappings. + -- Takes precedence over default spell maps. + -- Is reset at the end of each spell casting cycle (ie: at the end of aftercast). + classes.CustomClass = nil + classes.JAMode = nil + -- Custom groups used for defining melee and idle sets. Persists long-term. + classes.CustomMeleeGroups = L{} + classes.CustomRangedGroups = L{} + classes.CustomIdleGroups = L{} + classes.CustomDefenseGroups = L{} + + -- Class variables for time-based flags + classes.Daytime = false + classes.DuskToDawn = false + + + -- Special control flags. + mote_vars = {} + mote_vars.show_set = nil + mote_vars.set_breadcrumbs = L{} + + -- Display text mapping. + on_off_names = {[true] = 'on', [false] = 'off'} + on_off_values = T{'on', 'off', 'true', 'false'} + true_values = T{'on', 'true'} + + + -- Subtables within the sets table that we expect to exist, and are annoying to have to + -- define within each individual job file. We can define them here to make sure we don't + -- have to check for existence. The job file should be including this before defining + -- any sets, so any changes it makes will override these anyway. + sets.precast = {} + sets.precast.FC = {} + sets.precast.JA = {} + sets.precast.WS = {} + sets.precast.RA = {} + sets.midcast = {} + sets.midcast.RA = {} + sets.midcast.Pet = {} + sets.idle = {} + sets.resting = {} + sets.engaged = {} + sets.defense = {} + sets.buff = {} + + gear = {} + gear.default = {} + + gear.ElementalGorget = {name=""} + gear.ElementalBelt = {name=""} + gear.ElementalObi = {name=""} + gear.ElementalCape = {name=""} + gear.ElementalRing = {name=""} + gear.FastcastStaff = {name=""} + gear.RecastStaff = {name=""} + + + -- Load externally-defined information (info that we don't want to change every time this file is updated). + + -- Used to define misc utility functions that may be useful for this include or any job files. + include('Mote-Utility') + + -- Used for all self-command handling. + include('Mote-SelfCommands') + + -- Include general user globals, such as custom binds or gear tables. + -- Load Mote-Globals first, followed by User-Globals, followed by <character>-Globals. + -- Any functions re-defined in the later includes will overwrite the earlier versions. + include('Mote-Globals') + optional_include({'user-globals.lua'}) + optional_include({player.name..'-globals.lua'}) + + -- *-globals.lua may define additional sets to be added to the local ones. + if define_global_sets then + define_global_sets() + end + + -- Global default binds (either from Mote-Globals or user-globals) + (binds_on_load or global_on_load)() + + -- Load a sidecar file for the job (if it exists) that may re-define init_gear_sets and file_unload. + load_sidecar(player.main_job) + + -- General var initialization and setup. + if job_setup then + job_setup() + end + + -- User-specific var initialization and setup. + if user_setup then + user_setup() + end + + -- Load up all the gear sets. + init_gear_sets() +end + +-- Auto-initialize the include +init_include() + +-- Called when this job file is unloaded (eg: job change) +-- Conditional definition so that it doesn't overwrite explicit user +-- versions of this function. +if not file_unload then + file_unload = function() + if user_unload then + user_unload() + elseif job_file_unload then + job_file_unload() + end + _G[(binds_on_unload and 'binds_on_unload') or 'global_on_unload']() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Generalized functions for handling precast/midcast/aftercast for player-initiated actions. +-- This depends on proper set naming. +-- Global hooks can be written as user_xxx() to override functions at a global level. +-- Each job can override any of these general functions using job_xxx() hooks. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------ +-- Generic function to map a set processing order to all action events. +------------------------------------------------------------------------ + + +-- Process actions in a specific order of events: +-- Filter - filter_xxx() functions determine whether to run any of the code for this action. +-- Global - user_xxx() functions get called first. Define in Mote-Globals or User-Globals. +-- Local - job_xxx() functions get called next. Define in JOB.lua file. +-- Default - default_xxx() functions get called next. Defined in this file. +-- Cleanup - cleanup_xxx() functions always get called before exiting. +-- +-- Parameters: +-- spell - standard spell table passed in by GearSwap +-- action - string defining the function mapping to use (precast, midcast, etc) +function handle_actions(spell, action) + -- Init an eventArgs that allows cancelling. + local eventArgs = {handled = false, cancel = false} + + mote_vars.set_breadcrumbs:clear() + + -- Get the spell mapping, since we'll be passing it to various functions and checks. + local spellMap = get_spell_map(spell) + + -- General filter checks to see whether this function should be run. + -- If eventArgs.cancel is set, cancels this function, not the spell. + if _G['filter_'..action] then + _G['filter_'..action](spell, spellMap, eventArgs) + end + + -- If filter didn't cancel it, process user and default actions. + if not eventArgs.cancel then + -- Global user handling of this action + if _G['user_'..action] then + _G['user_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Job-specific handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['job_'..action] then + _G['job_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Default handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['default_'..action] then + _G['default_'..action](spell, spellMap) + display_breadcrumbs(spell, spellMap, action) + end + + -- Job-specific post-handling of this action + if not eventArgs.cancel and _G['job_post_'..action] then + _G['job_post_'..action](spell, action, spellMap, eventArgs) + end + end + + -- Cleanup once this action is done + if _G['cleanup_'..action] then + _G['cleanup_'..action](spell, spellMap, eventArgs) + end +end + + +-------------------------------------- +-- Action hooks called by GearSwap. +-------------------------------------- + +function pretarget(spell) + handle_actions(spell, 'pretarget') +end + +function precast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = true + end + handle_actions(spell, 'precast') +end + +function midcast(spell) + handle_actions(spell, 'midcast') +end + +function aftercast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = not spell.interrupted or buffactive[spell.english] or false + end + handle_actions(spell, 'aftercast') +end + +function pet_midcast(spell) + handle_actions(spell, 'pet_midcast') +end + +function pet_aftercast(spell) + handle_actions(spell, 'pet_aftercast') +end + +-------------------------------------- +-- Default code for each action. +-------------------------------------- + +function default_pretarget(spell, spellMap) + auto_change_target(spell, spellMap) +end + +function default_precast(spell, spellMap) + equip(get_precast_set(spell, spellMap)) +end + +function default_midcast(spell, spellMap) + equip(get_midcast_set(spell, spellMap)) +end + +function default_aftercast(spell, spellMap) + if not pet_midaction() then + handle_equipping_gear(player.status) + end +end + +function default_pet_midcast(spell, spellMap) + equip(get_pet_midcast_set(spell, spellMap)) +end + +function default_pet_aftercast(spell, spellMap) + handle_equipping_gear(player.status) +end + +-------------------------------------- +-- Filters for each action. +-- Set eventArgs.cancel to true to stop further processing. +-- May show notification messages, but should not do any processing here. +-------------------------------------- + +function filter_midcast(spell, spellMap, eventArgs) + if mote_vars.show_set == 'precast' then + eventArgs.cancel = true + end +end + +function filter_aftercast(spell, spellMap, eventArgs) + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' or mote_vars.show_set == 'pet_midcast' then + eventArgs.cancel = true + elseif spell.name == 'Unknown Interrupt' then + eventArgs.cancel = true + end +end + +function filter_pet_midcast(spell, spellMap, eventArgs) + -- If we have show_set active for precast or midcast, don't try to equip pet midcast gear. + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' then + add_to_chat(104, 'Show Sets: Pet midcast not equipped.') + eventArgs.cancel = true + end +end + +function filter_pet_aftercast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast or midcast, don't try to equip aftercast gear. + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' or mote_vars.show_set == 'pet_midcast' then + eventArgs.cancel = true + end +end + +-------------------------------------- +-- Cleanup code for each action. +-------------------------------------- + +function cleanup_precast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'precast' then + add_to_chat(104, 'Show Sets: Stopping at precast.') + end +end + +function cleanup_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for midcast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'midcast' then + add_to_chat(104, 'Show Sets: Stopping at midcast.') + end +end + +function cleanup_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + -- If we're in the middle of a pet action, pet_aftercast will handle clearing it. + if not pet_midaction() then + reset_transitory_classes() + end +end + +function cleanup_pet_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for pet midcast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'pet_midcast' then + add_to_chat(104, 'Show Sets: Stopping at pet midcast.') + end +end + +function cleanup_pet_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + reset_transitory_classes() +end + + +-- Clears the values from classes that only exist til the action is complete. +function reset_transitory_classes() + classes.CustomClass = nil + classes.JAMode = nil +end + + + +------------------------------------------------------------------------------------------------------------------- +-- High-level functions for selecting and equipping gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Central point to call to equip gear based on status. +-- Status - Player status that we're using to define what gear to equip. +function handle_equipping_gear(playerStatus, petStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_handle_equipping_gear then + job_handle_equipping_gear(playerStatus, eventArgs) + end + + -- Equip default gear if job didn't handle it. + if not eventArgs.handled then + equip_gear_by_status(playerStatus, petStatus) + end +end + + +-- Function to wrap logic for equipping gear on aftercast, status change, or user update. +-- @param status : The current or new player status that determines what sort of gear to equip. +function equip_gear_by_status(playerStatus, petStatus) + if _global.debug_mode then add_to_chat(123,'Debug: Equip gear for status ['..tostring(status)..'], HP='..tostring(player.hp)) end + + playerStatus = playerStatus or player.status or 'Idle' + + -- If status not defined, treat as idle. + -- Be sure to check for positive HP to make sure they're not dead. + if (playerStatus == 'Idle' or playerStatus == '') and player.hp > 0 then + equip(get_idle_set(petStatus)) + elseif playerStatus == 'Engaged' then + equip(get_melee_set(petStatus)) + elseif playerStatus == 'Resting' then + equip(get_resting_set(petStatus)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on status. +------------------------------------------------------------------------------------------------------------------- + +-- Returns the appropriate idle set based on current state values and location. +-- Set construction order (all of which are optional): +-- sets.idle[idleScope][state.IdleMode][Pet[Engaged]][CustomIdleGroups] +-- +-- Params: +-- petStatus - Optional explicit definition of pet status. +function get_idle_set(petStatus) + local idleSet = sets.idle + + if not idleSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('idle') + + local idleScope + + if buffactive.weakness then + idleScope = 'Weak' + elseif areas.Cities:contains(world.area) then + idleScope = 'Town' + else + idleScope = 'Field' + end + + if idleSet[idleScope] then + idleSet = idleSet[idleScope] + mote_vars.set_breadcrumbs:append(idleScope) + end + + if idleSet[state.IdleMode] then + idleSet = idleSet[state.IdleMode] + mote_vars.set_breadcrumbs:append(state.IdleMode) + end + + if (pet.isvalid or state.Buff.Pet) and idleSet.Pet then + idleSet = idleSet.Pet + petStatus = petStatus or pet.status + mote_vars.set_breadcrumbs:append('Pet') + + if petStatus == 'Engaged' and idleSet.Engaged then + idleSet = idleSet.Engaged + mote_vars.set_breadcrumbs:append('Engaged') + end + end + + for _,group in ipairs(classes.CustomIdleGroups) do + if idleSet[group] then + idleSet = idleSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + idleSet = apply_defense(idleSet) + idleSet = apply_kiting(idleSet) + + if user_customize_idle_set then + idleSet = user_customize_idle_set(idleSet) + end + + if customize_idle_set then + idleSet = customize_idle_set(idleSet) + end + + return idleSet +end + + +-- Returns the appropriate melee set based on current state values. +-- Set construction order (all sets after sets.engaged are optional): +-- sets.engaged[state.CombatForm][state.CombatWeapon][state.OffenseMode][state.DefenseMode][classes.CustomMeleeGroups (any number)] +function get_melee_set() + local meleeSet = sets.engaged + + if not meleeSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('engaged') + + if state.CombatForm and meleeSet[state.CombatForm] then + meleeSet = meleeSet[state.CombatForm] + mote_vars.set_breadcrumbs:append(state.CombatForm) + end + + if state.CombatWeapon and meleeSet[state.CombatWeapon] then + meleeSet = meleeSet[state.CombatWeapon] + mote_vars.set_breadcrumbs:append(state.CombatWeapon) + end + + if meleeSet[state.OffenseMode] then + meleeSet = meleeSet[state.OffenseMode] + mote_vars.set_breadcrumbs:append(state.OffenseMode) + end + + if meleeSet[state.DefenseMode] then + meleeSet = meleeSet[state.DefenseMode] + mote_vars.set_breadcrumbs:append(state.DefenseMode) + end + + for _,group in ipairs(classes.CustomMeleeGroups) do + if meleeSet[group] then + meleeSet = meleeSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + meleeSet = apply_defense(meleeSet) + meleeSet = apply_kiting(meleeSet) + + if customize_melee_set then + meleeSet = customize_melee_set(meleeSet) + end + + if user_customize_melee_set then + meleeSet = user_customize_melee_set(meleeSet) + end + + return meleeSet +end + + +-- Returns the appropriate resting set based on current state values. +-- Set construction order: +-- sets.resting[state.RestingMode] +function get_resting_set() + local restingSet = sets.resting + + if not restingSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('resting') + + if restingSet[state.RestingMode] then + restingSet = restingSet[state.RestingMode] + mote_vars.set_breadcrumbs:append(state.RestingMode) + end + + return restingSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on action. +------------------------------------------------------------------------------------------------------------------- + +-- Get the default precast gear set. +function get_precast_set(spell, spellMap) + -- If there are no precast sets defined, bail out. + if not sets.precast then + return {} + end + + local equipSet = sets.precast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('precast') + + -- Determine base sub-table from type of action being performed. + + local cat + + if spell.action_type == 'Magic' then + cat = 'FC' + elseif spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Ability' then + if spell.type == 'WeaponSkill' then + cat = 'WS' + elseif spell.type == 'JobAbility' then + cat = 'JA' + else + -- Allow fallback to .JA table if spell.type isn't found, for all non-weaponskill abilities. + cat = (sets.precast[spell.type] and spell.type) or 'JA' + end + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = false + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + + -- Once we have a named base set, do checks for specialized modes (casting mode, weaponskill mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.type == 'WeaponSkill' then + equipSet = get_weaponskill_set(equipSet, spell, spellMap) + elseif spell.action_type == 'Ability' then + if classes.JAMode and equipSet[classes.JAMode] then + equipSet = equipSet[classes.JAMode] + mote_vars.set_breadcrumbs:append(classes.JAMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Update defintions for element-specific gear that may be used. + set_elemental_gear(spell) + + -- Return whatever we've constructed. + return equipSet +end + + + +-- Get the default midcast gear set. +-- This builds on sets.midcast. +function get_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast then + return {} + end + + local equipSet = sets.midcast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + + -- Determine base sub-table from type of action being performed. + -- Only ranged attacks and items get specific sub-categories here. + + local cat + + if spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = classes.NoSkillSpells:contains(spell.english) + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- After the default checks, do checks for specialized modes (casting mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Return whatever we've constructed. + return equipSet +end + + +-- Get the default pet midcast gear set. +-- This is built in sets.midcast.Pet. +function get_pet_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast or not sets.midcast.Pet then + return {} + end + + local equipSet = sets.midcast.Pet + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + mote_vars.set_breadcrumbs:append('Pet') + + if sets.midcast and sets.midcast.Pet then + classes.SkipSkillCheck = false + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- We can only generally be certain about whether the pet's action is + -- Magic (ie: it cast a spell of its own volition) or Ability (it performed + -- an action at the request of the player). Allow CastinMode and + -- OffenseMode to refine whatever set was selected above. + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.action_type == 'Ability' then + if equipSet[state.OffenseMode] then + equipSet = equipSet[state.OffenseMode] + mote_vars.set_breadcrumbs:append(state.OffenseMode) + end + end + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper weaponskill set. +function get_weaponskill_set(equipSet, spell, spellMap) + -- Custom handling for weaponskills + local ws_mode = state.WeaponskillMode + + if ws_mode == 'Normal' then + -- If a particular weaponskill mode isn't specified, see if we have a weaponskill mode + -- corresponding to the current offense mode. If so, use that. + if spell.skill == 'Archery' or spell.skill == 'Marksmanship' then + if state.RangedMode ~= 'Normal' and S(options.WeaponskillModes):contains(state.RangedMode) then + ws_mode = state.RangedMode + end + else + if state.OffenseMode ~= 'Normal' and S(options.WeaponskillModes):contains(state.OffenseMode) then + ws_mode = state.OffenseMode + end + end + end + + local custom_wsmode + + -- Allow the job file to specify a preferred weaponskill mode + if get_custom_wsmode then + custom_wsmode = get_custom_wsmode(spell, spellMap, ws_mode) + end + + -- If the job file returned a weaponskill mode, use that. + if custom_wsmode then + ws_mode = custom_wsmode + end + + if equipSet[ws_mode] then + equipSet = equipSet[ws_mode] + mote_vars.set_breadcrumbs:append(ws_mode) + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper ranged set. +function get_ranged_set(equipSet, spell, spellMap) + -- Attach Combat Form and Combat Weapon to set checks + if state.CombatForm and equipSet[state.CombatForm] then + equipSet = equipSet[state.CombatForm] + mote_vars.set_breadcrumbs:append(state.CombatForm) + end + + if state.CombatWeapon and equipSet[state.CombatWeapon] then + equipSet = equipSet[state.CombatWeapon] + mote_vars.set_breadcrumbs:append(state.CombatWeapon) + end + + -- Check for specific mode for ranged attacks (eg: Acc, Att, etc) + if equipSet[state.RangedMode] then + equipSet = equipSet[state.RangedMode] + mote_vars.set_breadcrumbs:append(state.RangedMode) + end + + -- Tack on any additionally specified custom groups, if the sets are defined. + for _,group in ipairs(classes.CustomRangedGroups) do + if equipSet[group] then + equipSet = equipSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + return equipSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for optional supplemental gear overriding the default sets defined above. +------------------------------------------------------------------------------------------------------------------- + +-- Function to apply any active defense set on top of the supplied set +-- @param baseSet : The set that any currently active defense set will be applied on top of. (gear set table) +function apply_defense(baseSet) + if state.Defense.Active then + local defenseSet = sets.defense + + if state.Defense.Type == 'Physical' then + defenseSet = sets.defense[state.Defense.PhysicalMode] or defenseSet + else + defenseSet = sets.defense[state.Defense.MagicalMode] or defenseSet + end + + for _,group in ipairs(classes.CustomDefenseGroups) do + defenseSet = defenseSet[group] or defenseSet + end + + baseSet = set_combine(baseSet, defenseSet) + end + + return baseSet +end + + +-- Function to add kiting gear on top of the base set if kiting state is true. +-- @param baseSet : The gear set that the kiting gear will be applied on top of. +function apply_kiting(baseSet) + if state.Kiting then + if sets.Kiting then + baseSet = set_combine(baseSet, sets.Kiting) + end + end + + return baseSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for constructing default gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Get a spell mapping for the spell. +function get_spell_map(spell) + local defaultSpellMap = classes.SpellMaps[spell.english] + local jobSpellMap + + if job_get_spell_map then + jobSpellMap = job_get_spell_map(spell, defaultSpellMap) + end + + return jobSpellMap or defaultSpellMap +end + + +-- Select the equipment set to equip from a given starting table, based on standard +-- selection order: custom class, spell name, spell map, spell skill, and spell type. +-- Spell skill and spell type may further refine their selections based on +-- custom class, spell name and spell map. +function select_specific_set(equipSet, spell, spellMap) + -- Take the determined base equipment set and try to get the simple naming extensions that + -- may apply to it (class, spell name, spell map). + local namedSet = get_named_set(equipSet, spell, spellMap) + + -- If no simple naming sub-tables were found, and we simply got back the original equip set, + -- check for spell.skill and spell.type, then check the simple naming extensions again. + if namedSet == equipSet then + if spell.skill and equipSet[spell.skill] and not classes.SkipSkillCheck then + namedSet = equipSet[spell.skill] + mote_vars.set_breadcrumbs:append(spell.skill) + elseif spell.type and equipSet[spell.type] then + namedSet = equipSet[spell.type] + mote_vars.set_breadcrumbs:append(spell.type) + else + return equipSet + end + + namedSet = get_named_set(namedSet, spell, spellMap) + end + + return namedSet or equipSet +end + + +-- Simple utility function to handle a portion of the equipment set determination. +-- It attempts to select a sub-table of the provided equipment set based on the +-- standard search order of custom class, spell name, and spell map. +-- If no such set is found, it returns the original base set (equipSet) provided. +function get_named_set(equipSet, spell, spellMap) + if equipSet then + if classes.CustomClass and equipSet[classes.CustomClass] then + mote_vars.set_breadcrumbs:append(classes.CustomClass) + return equipSet[classes.CustomClass] + elseif equipSet[spell.english] then + mote_vars.set_breadcrumbs:append(spell.english) + return equipSet[spell.english] + elseif spellMap and equipSet[spellMap] then + mote_vars.set_breadcrumbs:append(spellMap) + return equipSet[spellMap] + else + return equipSet + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Hooks for other events. +------------------------------------------------------------------------------------------------------------------- + +-- Called when the player's subjob changes. +function sub_job_change(newSubjob, oldSubjob) + if user_setup then + user_setup() + end + + if job_sub_job_change then + job_sub_job_change(newSubjob, oldSubjob) + end + + send_command('gs c update') +end + + +-- Called when the player's status changes. +function status_change(newStatus, oldStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + mote_vars.set_breadcrumbs:clear() + + -- Allow a global function to be called on status change. + if user_status_change then + user_status_change(newStatus, oldStatus, eventArgs) + end + + -- Then call individual jobs to handle status change events. + if not eventArgs.handled then + if job_status_change then + job_status_change(newStatus, oldStatus, eventArgs) + end + end + + -- Handle equipping default gear if the job didn't mark this as handled. + if not eventArgs.handled then + handle_equipping_gear(newStatus) + display_breadcrumbs() + end +end + + +-- Called when a player gains or loses a buff. +-- buff == buff gained or lost +-- gain == true if the buff was gained, false if it was lost. +function buff_change(buff, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + if state.Buff[buff] ~= nil then + state.Buff[buff] = gain + end + + -- Allow a global function to be called on buff change. + if user_buff_change then + user_buff_change(buff, gain, eventArgs) + end + + -- Allow jobs to handle buff change events. + if not eventArgs.handled then + if job_buff_change then + job_buff_change(buff, gain, eventArgs) + end + end +end + + +-- Called when a player gains or loses a pet. +-- pet == pet gained or lost +-- gain == true if the pet was gained, false if it was lost. +function pet_change(pet, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to handle pet change events. + if job_pet_change then + job_pet_change(pet, gain, eventArgs) + end + + -- Equip default gear if not handled by the job. + if not eventArgs.handled then + handle_equipping_gear(player.status) + end +end + + +-- Called when the player's pet's status changes. +-- Note that this is also called after pet_change when the pet is released. +-- As such, don't automatically handle gear equips. Only do so if directed +-- to do so by the job. +function pet_status_change(newStatus, oldStatus) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_pet_status_change then + job_pet_status_change(newStatus, oldStatus, eventArgs) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Debugging functions. +------------------------------------------------------------------------------------------------------------------- + +-- This is a debugging function that will print the accumulated set selection +-- breadcrumbs for the default selected set for any given action stage. +function display_breadcrumbs(spell, spellMap, action) + if not _settings.debug_mode then + return + end + + local msg = 'Default ' + + if action and spell then + msg = msg .. action .. ' set selection for ' .. spell.name + end + + if spellMap then + msg = msg .. ' (' .. spellMap .. ')' + end + msg = msg .. ' : ' + + local cons + + for _,name in ipairs(mote_vars.set_breadcrumbs) do + if not cons then + cons = name + else + if name:contains(' ') or name:contains("'") then + cons = cons .. '["' .. name .. '"]' + else + cons = cons .. '.' .. name + end + end + end + + if cons then + if action and cons == ('sets.' .. action) then + msg = msg .. "None" + else + msg = msg .. tostring(cons) + end + add_to_chat(123, msg) + end +end + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua new file mode 100644 index 0000000..76297a3 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua @@ -0,0 +1,273 @@ +------------------------------------------------------------------------------------------------------------------- +-- Mappings, lists and sets to describe game relationships that aren't easily determinable otherwise. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Elemental mappings for element relationships and certain types of spells and gear. +------------------------------------------------------------------------------------------------------------------- + +-- Basic elements +elements = {} + +elements.list = S{'Light','Dark','Fire','Ice','Wind','Earth','Lightning','Water'} + +elements.weak_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Ice', ['Ice']='Wind', ['Wind']='Earth', ['Earth']='Lightning', + ['Lightning']='Water', ['Water']='Fire'} + +elements.strong_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Water', ['Ice']='Fire', ['Wind']='Ice', ['Earth']='Wind', + ['Lightning']='Earth', ['Water']='Lightning'} + + +storms = S{"Aurorastorm", "Voidstorm", "Firestorm", "Sandstorm", "Rainstorm", "Windstorm", "Hailstorm", "Thunderstorm"} +elements.storm_of = {['Light']="Aurorastorm", ['Dark']="Voidstorm", ['Fire']="Firestorm", ['Earth']="Sandstorm", + ['Water']="Rainstorm", ['Wind']="Windstorm", ['Ice']="Hailstorm", ['Lightning']="Thunderstorm"} + +spirits = S{"LightSpirit", "DarkSpirit", "FireSpirit", "EarthSpirit", "WaterSpirit", "AirSpirit", "IceSpirit", "ThunderSpirit"} +elements.spirit_of = {['Light']="Light Spirit", ['Dark']="Dark Spirit", ['Fire']="Fire Spirit", ['Earth']="Earth Spirit", + ['Water']="Water Spirit", ['Wind']="Air Spirit", ['Ice']="Ice Spirit", ['Lightning']="Thunder Spirit"} + +runes = S{'Lux', 'Tenebrae', 'Ignis', 'Gelus', 'Flabra', 'Tellus', 'Sulpor', 'Unda'} +elements.rune_of = {['Light']='Lux', ['Dark']='Tenebrae', ['Fire']='Ignis', ['Ice']='Gelus', ['Wind']='Flabra', + ['Earth']='Tellus', ['Lightning']='Sulpor', ['Water']='Unda'} + +elements.obi_of = {['Light']='Hachirin-no-obi', ['Dark']='Hachirin-no-obi', ['Fire']='Hachirin-no-obi', ['Ice']='Hachirin-no-obi', ['Wind']='Hachirin-no-obi', + ['Earth']='Hachirin-no-obi', ['Lightning']='Hachirin-no-obi', ['Water']='Hachirin-no-obi'} + +elements.gorget_of = {['Light']='Fotia Gorget', ['Dark']='Fotia Gorget', ['Fire']='Fotia Gorget', ['Ice']='Fotia Gorget', + ['Wind']='Fotia Gorget', ['Earth']='Fotia Gorget', ['Lightning']='Fotia Gorget', ['Water']='Fotia Gorget'} + +elements.belt_of = {['Light']='Fotia Belt', ['Dark']='Fotia Belt', ['Fire']='Fotia Belt', ['Ice']='Fotia Belt', + ['Wind']='Fotia Belt', ['Earth']='Fotia Belt', ['Lightning']='Fotia Belt', ['Water']='Fotia Belt'} + +elements.fastcast_staff_of = {['Light']='Arka I', ['Dark']='Xsaeta I', ['Fire']='Atar I', ['Ice']='Vourukasha I', + ['Wind']='Vayuvata I', ['Earth']='Vishrava I', ['Lightning']='Apamajas I', ['Water']='Haoma I', ['Thunder']='Apamajas I'} + +elements.recast_staff_of = {['Light']='Arka II', ['Dark']='Xsaeta II', ['Fire']='Atar II', ['Ice']='Vourukasha II', + ['Wind']='Vayuvata II', ['Earth']='Vishrava II', ['Lightning']='Apamajas II', ['Water']='Haoma II', ['Thunder']='Apamajas II'} + +elements.perpetuance_staff_of = {['Light']='Arka III', ['Dark']='Xsaeta III', ['Fire']='Atar III', ['Ice']='Vourukasha III', + ['Wind']='Vayuvata III', ['Earth']='Vishrava III', ['Lightning']='Apamajas III', ['Water']='Haoma III', ['Thunder']='Apamajas III'} + + +-- Elements for skillchain names +skillchain_elements = {} +skillchain_elements.Light = S{'Light','Fire','Wind','Lightning'} +skillchain_elements.Darkness = S{'Dark','Ice','Earth','Water'} +skillchain_elements.Fusion = S{'Light','Fire'} +skillchain_elements.Fragmentation = S{'Wind','Lightning'} +skillchain_elements.Distortion = S{'Ice','Water'} +skillchain_elements.Gravitation = S{'Dark','Earth'} +skillchain_elements.Transfixion = S{'Light'} +skillchain_elements.Compression = S{'Dark'} +skillchain_elements.Liquification = S{'Fire'} +skillchain_elements.Induration = S{'Ice'} +skillchain_elements.Detonation = S{'Wind'} +skillchain_elements.Scission = S{'Earth'} +skillchain_elements.Impaction = S{'Lightning'} +skillchain_elements.Reverberation = S{'Water'} + + +------------------------------------------------------------------------------------------------------------------- +-- Mappings for weaponskills +------------------------------------------------------------------------------------------------------------------- + +-- REM weapons and their corresponding weaponskills +data = {} +data.weaponskills = {} +data.weaponskills.relic = { + ["Spharai"] = "Final Heaven", + ["Mandau"] = "Mercy Stroke", + ["Excalibur"] = "Knights of Round", + ["Ragnarok"] = "Scourge", + ["Guttler"] = "Onslaught", + ["Bravura"] = "Metatron Torment", + ["Apocalypse"] = "Catastrophe", + ["Gungnir"] = "Gierskogul", + ["Kikoku"] = "Blade: Metsu", + ["Amanomurakumo"] = "Tachi: Kaiten", + ["Mjollnir"] = "Randgrith", + ["Claustrum"] = "Gates of Tartarus", + ["Annihilator"] = "Coronach", + ["Yoichinoyumi"] = "Namas Arrow"} +data.weaponskills.mythic = { + ["Conqueror"] = "King's Justice", + ["Glanzfaust"] = "Ascetic's Fury", + ["Yagrush"] = "Mystic Boon", + ["Laevateinn"] = "Vidohunir", + ["Murgleis"] = "Death Blossom", + ["Vajra"] = "Mandalic Stab", + ["Burtgang"] = "Atonement", + ["Liberator"] = "Insurgency", + ["Aymur"] = "Primal Rend", + ["Carnwenhan"] = "Mordant Rime", + ["Gastraphetes"] = "Trueflight", + ["Kogarasumaru"] = "Tachi: Rana", + ["Nagi"] = "Blade: Kamu", + ["Ryunohige"] = "Drakesbane", + ["Nirvana"] = "Garland of Bliss", + ["Tizona"] = "Expiacion", + ["Death Penalty"] = "Leaden Salute", + ["Kenkonken"] = "Stringing Pummel", + ["Terpsichore"] = "Pyrrhic Kleos", + ["Tupsimati"] = "Omniscience", + ["Idris"] = "Exudation", + ["Epeolatry"] = "Dimidiation"} +data.weaponskills.empyrean = { + ["Verethragna"] = "Victory Smite", + ["Twashtar"] = "Rudra's Storm", + ["Almace"] = "Chant du Cygne", + ["Caladbolg"] = "Torcleaver", + ["Farsha"] = "Cloudsplitter", + ["Ukonvasara"] = "Ukko's Fury", + ["Redemption"] = "Quietus", + ["Rhongomiant"] = "Camlann's Torment", + ["Kannagi"] = "Blade: Hi", + ["Masamune"] = "Tachi: Fudo", + ["Gambanteinn"] = "Dagann", + ["Hvergelmir"] = "Myrkr", + ["Gandiva"] = "Jishnu's Radiance", + ["Armageddon"] = "Wildfire"} + +-- Weaponskills that can be used at range. +data.weaponskills.ranged = S{"Flaming Arrow", "Piercing Arrow", "Dulling Arrow", "Sidewinder", "Arching Arrow", + "Empyreal Arrow", "Refulgent Arrow", "Apex Arrow", "Namas Arrow", "Jishnu's Radiance", + "Hot Shot", "Split Shot", "Sniper Shot", "Slug Shot", "Heavy Shot", "Detonator", "Last Stand", + "Coronach", "Trueflight", "Leaden Salute", "Wildfire", + "Myrkr"} + +ranged_weaponskills = data.weaponskills.ranged + +------------------------------------------------------------------------------------------------------------------- +-- Spell mappings allow defining a general category or description that each of sets of related +-- spells all fall under. +------------------------------------------------------------------------------------------------------------------- + +spell_maps = { + ['Cure']='Cure',['Cure II']='Cure',['Cure III']='Cure',['Cure IV']='Cure',['Cure V']='Cure',['Cure VI']='Cure', + ['Cura']='Curaga',['Cura II']='Curaga',['Cura III']='Curaga', + ['Curaga']='Curaga',['Curaga II']='Curaga',['Curaga III']='Curaga',['Curaga IV']='Curaga',['Curaga V']='Curaga', + -- Status Removal doesn't include Esuna or Sacrifice, since they work differently than the rest + ['Poisona']='StatusRemoval',['Paralyna']='StatusRemoval',['Silena']='StatusRemoval',['Blindna']='StatusRemoval',['Cursna']='StatusRemoval', + ['Stona']='StatusRemoval',['Viruna']='StatusRemoval',['Erase']='StatusRemoval', + ['Barfire']='BarElement',['Barstone']='BarElement',['Barwater']='BarElement',['Baraero']='BarElement',['Barblizzard']='BarElement',['Barthunder']='BarElement', + ['Barfira']='BarElement',['Barstonra']='BarElement',['Barwatera']='BarElement',['Baraera']='BarElement',['Barblizzara']='BarElement',['Barthundra']='BarElement', + ['Raise']='Raise',['Raise II']='Raise',['Raise III']='Raise',['Arise']='Raise', + ['Reraise']='Reraise',['Reraise II']='Reraise',['Reraise III']='Reraise', + ['Protect']='Protect',['Protect II']='Protect',['Protect III']='Protect',['Protect IV']='Protect',['Protect V']='Protect', + ['Shell']='Shell',['Shell II']='Shell',['Shell III']='Shell',['Shell IV']='Shell',['Shell V']='Shell', + ['Protectra']='Protectra',['Protectra II']='Protectra',['Protectra III']='Protectra',['Protectra IV']='Protectra',['Protectra V']='Protectra', + ['Shellra']='Shellra',['Shellra II']='Shellra',['Shellra III']='Shellra',['Shellra IV']='Shellra',['Shellra V']='Shellra', + ['Regen']='Regen',['Regen II']='Regen',['Regen III']='Regen',['Regen IV']='Regen',['Regen V']='Regen', + ['Refresh']='Refresh',['Refresh II']='Refresh', + ['Teleport-Holla']='Teleport',['Teleport-Dem']='Teleport',['Teleport-Mea']='Teleport',['Teleport-Altep']='Teleport',['Teleport-Yhoat']='Teleport', + ['Teleport-Vahzl']='Teleport',['Recall-Pashh']='Teleport',['Recall-Meriph']='Teleport',['Recall-Jugner']='Teleport', + ['Valor Minuet']='Minuet',['Valor Minuet II']='Minuet',['Valor Minuet III']='Minuet',['Valor Minuet IV']='Minuet',['Valor Minuet V']='Minuet', + ["Knight's Minne"]='Minne',["Knight's Minne II"]='Minne',["Knight's Minne III"]='Minne',["Knight's Minne IV"]='Minne',["Knight's Minne V"]='Minne', + ['Advancing March']='March',['Victory March']='March', + ['Sword Madrigal']='Madrigal',['Blade Madrigal']='Madrigal', + ["Hunter's Prelude"]='Prelude',["Archer's Prelude"]='Prelude', + ['Sheepfoe Mambo']='Mambo',['Dragonfoe Mambo']='Mambo', + ['Raptor Mazurka']='Mazurka',['Chocobo Mazurka']='Mazurka', + ['Sinewy Etude']='Etude',['Dextrous Etude']='Etude',['Vivacious Etude']='Etude',['Quick Etude']='Etude',['Learned Etude']='Etude',['Spirited Etude']='Etude',['Enchanting Etude']='Etude', + ['Herculean Etude']='Etude',['Uncanny Etude']='Etude',['Vital Etude']='Etude',['Swift Etude']='Etude',['Sage Etude']='Etude',['Logical Etude']='Etude',['Bewitching Etude']='Etude', + ["Mage's Ballad"]='Ballad',["Mage's Ballad II"]='Ballad',["Mage's Ballad III"]='Ballad', + ["Army's Paeon"]='Paeon',["Army's Paeon II"]='Paeon',["Army's Paeon III"]='Paeon',["Army's Paeon IV"]='Paeon',["Army's Paeon V"]='Paeon',["Army's Paeon VI"]='Paeon', + ['Fire Carol']='Carol',['Ice Carol']='Carol',['Wind Carol']='Carol',['Earth Carol']='Carol',['Lightning Carol']='Carol',['Water Carol']='Carol',['Light Carol']='Carol',['Dark Carol']='Carol', + ['Fire Carol II']='Carol',['Ice Carol II']='Carol',['Wind Carol II']='Carol',['Earth Carol II']='Carol',['Lightning Carol II']='Carol',['Water Carol II']='Carol',['Light Carol II']='Carol',['Dark Carol II']='Carol', + ['Foe Lullaby']='Lullaby',['Foe Lullaby II']='Lullaby',['Horde Lullaby']='Lullaby',['Horde Lullaby II']='Lullaby', + ['Fire Threnody']='Threnody',['Ice Threnody']='Threnody',['Wind Threnody']='Threnody',['Earth Threnody']='Threnody',['Lightning Threnody']='Threnody',['Water Threnody']='Threnody',['Light Threnody']='Threnody',['Dark Threnody']='Threnody', + ['Battlefield Elegy']='Elegy',['Carnage Elegy']='Elegy', + ['Foe Requiem']='Requiem',['Foe Requiem II']='Requiem',['Foe Requiem III']='Requiem',['Foe Requiem IV']='Requiem',['Foe Requiem V']='Requiem',['Foe Requiem VI']='Requiem',['Foe Requiem VII']='Requiem', + ['Utsusemi: Ichi']='Utsusemi',['Utsusemi: Ni']='Utsusemi', + ['Katon: Ichi'] = 'ElementalNinjutsu',['Suiton: Ichi'] = 'ElementalNinjutsu',['Raiton: Ichi'] = 'ElementalNinjutsu', + ['Doton: Ichi'] = 'ElementalNinjutsu',['Huton: Ichi'] = 'ElementalNinjutsu',['Hyoton: Ichi'] = 'ElementalNinjutsu', + ['Katon: Ni'] = 'ElementalNinjutsu',['Suiton: Ni'] = 'ElementalNinjutsu',['Raiton: Ni'] = 'ElementalNinjutsu', + ['Doton: Ni'] = 'ElementalNinjutsu',['Huton: Ni'] = 'ElementalNinjutsu',['Hyoton: Ni'] = 'ElementalNinjutsu', + ['Katon: San'] = 'ElementalNinjutsu',['Suiton: San'] = 'ElementalNinjutsu',['Raiton: San'] = 'ElementalNinjutsu', + ['Doton: San'] = 'ElementalNinjutsu',['Huton: San'] = 'ElementalNinjutsu',['Hyoton: San'] = 'ElementalNinjutsu', + ['Banish']='Banish',['Banish II']='Banish',['Banish III']='Banish',['Banishga']='Banish',['Banishga II']='Banish', + ['Holy']='Holy',['Holy II']='Holy',['Drain']='Drain',['Drain II']='Drain',['Aspir']='Aspir',['Aspir II']='Aspir', + ['Absorb-Str']='Absorb',['Absorb-Dex']='Absorb',['Absorb-Vit']='Absorb',['Absorb-Agi']='Absorb',['Absorb-Int']='Absorb',['Absorb-Mnd']='Absorb',['Absorb-Chr']='Absorb', + ['Absorb-Acc']='Absorb',['Absorb-TP']='Absorb',['Absorb-Attri']='Absorb', + ['Burn']='ElementalEnfeeble',['Frost']='ElementalEnfeeble',['Choke']='ElementalEnfeeble',['Rasp']='ElementalEnfeeble',['Shock']='ElementalEnfeeble',['Drown']='ElementalEnfeeble', + ['Pyrohelix']='Helix',['Cryohelix']='Helix',['Anemohelix']='Helix',['Geohelix']='Helix',['Ionohelix']='Helix',['Hydrohelix']='Helix',['Luminohelix']='Helix',['Noctohelix']='Helix', + ['Firestorm']='Storm',['Hailstorm']='Storm',['Windstorm']='Storm',['Sandstorm']='Storm',['Thunderstorm']='Storm',['Rainstorm']='Storm',['Aurorastorm']='Storm',['Voidstorm']='Storm', + ['Fire Maneuver']='Maneuver',['Ice Maneuver']='Maneuver',['Wind Maneuver']='Maneuver',['Earth Maneuver']='Maneuver',['Thunder Maneuver']='Maneuver', + ['Water Maneuver']='Maneuver',['Light Maneuver']='Maneuver',['Dark Maneuver']='Maneuver', +} + +no_skill_spells_list = S{'Haste', 'Refresh', 'Regen', 'Protect', 'Protectra', 'Shell', 'Shellra', + 'Raise', 'Reraise', 'Sneak', 'Invisible', 'Deodorize'} + + +------------------------------------------------------------------------------------------------------------------- +-- Tables to specify general area groupings. Creates the 'areas' table to be referenced in job files. +-- Zone names provided by world.area/world.zone are currently in all-caps, so defining the same way here. +------------------------------------------------------------------------------------------------------------------- + +areas = {} + +-- City areas for town gear and behavior. +areas.Cities = S{ + "Ru'Lude Gardens", + "Upper Jeuno", + "Lower Jeuno", + "Port Jeuno", + "Port Windurst", + "Windurst Waters", + "Windurst Woods", + "Windurst Walls", + "Heavens Tower", + "Port San d'Oria", + "Northern San d'Oria", + "Southern San d'Oria", + "Port Bastok", + "Bastok Markets", + "Bastok Mines", + "Metalworks", + "Aht Urhgan Whitegate", + "Tavanazian Safehold", + "Nashmau", + "Selbina", + "Mhaura", + "Norg", + "Eastern Adoulin", + "Western Adoulin", + "Kazham" +} +-- Adoulin areas, where Ionis will grant special stat bonuses. +areas.Adoulin = S{ + "Yahse Hunting Grounds", + "Ceizak Battlegrounds", + "Foret de Hennetiel", + "Morimar Basalt Fields", + "Yorcia Weald", + "Yorcia Weald [U]", + "Cirdas Caverns", + "Cirdas Caverns [U]", + "Marjami Ravine", + "Kamihr Drifts", + "Sih Gates", + "Moh Gates", + "Dho Gates", + "Woh Gates", + "Rala Waterways", + "Rala Waterways [U]", + "Outer Ra'Kaznar", + "Outer Ra'Kaznar [U]" +} + + +------------------------------------------------------------------------------------------------------------------- +-- Lists of certain NPCs. +------------------------------------------------------------------------------------------------------------------- + +npcs = {} +npcs.Trust = S{'Ajido-Marujido','Aldo','Ayame','Cherukiki','Curilla','D.Shantotto','Elivira','Excenmille', + 'Fablinix','FerreousCoffin','Gadalar','Gessho','Ingrid','IronEater','Joachim','Klara','Kupipi', + 'LehkoHabhoka','LhuMhakaracca','Lion','Luzaf','Maat','MihliAliapoh','Mnejing','Moogle','Mumor', + 'NajaSalaheem','Najelith','Naji','NanaaMihgo','Nashmeira','Noillurie','Ovjang','Prishe','Rainemard', + 'RomaaMihgo','Sakura','Shantotto','StarSibyl','Tenzen','Trion','UkaTotlihn','Ulmia','Valaineral', + 'Volker','Zazarg','Zeid'} + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua new file mode 100644 index 0000000..b15a70a --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua @@ -0,0 +1,715 @@ +------------------------------------------------------------------------------------------------------------------- +-- General functions for manipulating state values via self-commands. +-- Only handles certain specific states that we've defined, though it +-- allows the user to hook into the cycle command. +------------------------------------------------------------------------------------------------------------------- + +-- Routing function for general known self_commands. +-- Handles splitting the provided command line up into discrete words, for the other functions to use. +function self_command(commandArgs) + local commandArgs = commandArgs + if type(commandArgs) == 'string' then + commandArgs = T(commandArgs:split(' ')) + if #commandArgs == 0 then + return + end + end + + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_self_command then + job_self_command(commandArgs, eventArgs) + end + + if not eventArgs.handled then + -- Of the original command message passed in, remove the first word from + -- the list (it will be used to determine which function to call), and + -- send the remaining words as parameters for the function. + local handleCmd = table.remove(commandArgs, 1) + + if selfCommandMaps[handleCmd] then + selfCommandMaps[handleCmd](commandArgs) + end + end +end + + +-- Individual handling of self-commands + + +-- Handle toggling specific vars that we know of. +-- Valid toggles: Defense, Kiting +-- Returns true if a known toggle was handled. Returns false if not. +-- User command format: gs c toggle [field] +function handle_toggle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Toggle parameter failure: field not specified.') + return + end + + local toggleField = cmdParams[1]:lower() + local reportDescription + local notifyDescription + local oldVal + local newVal + + -- Known global states + if toggleField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = not state.Defense.Active + newVal = state.Defense.Active + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif toggleField == 'kite' or toggleField == 'kiting' then + oldVal = state.Kiting + state.Kiting = not state.Kiting + newVal = state.Kiting + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif toggleField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = not state.SelectNPCTargets + newVal = state.SelectNPCTargets + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[cmdParams[1]]) == 'boolean' then + oldVal = state[cmdParams[1]] + state[cmdParams[1]] = not state[cmdParams[1]] + newVal = state[cmdParams[1]] + notifyDescription = cmdParams[1] + reportDescription = cmdParams[1] + elseif job_toggle_state then + reportDescription, newVal = job_toggle_state(cmdParams[1]) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-GearSwap: Toggle: Unknown field ['..toggleField..']') + end +end + + +-- Function to handle turning on particular states, while possibly also setting a mode value. +-- User command format: gs c activate [field] +function handle_activate(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Activate parameter failure: field not specified.') + return + end + + local activateField = cmdParams[1]:lower() + local reportDescription + local notifyDescription + local oldVal + local newVal = true + + -- Known global states + if activateField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = true + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif activateField == 'physicaldefense' then + oldVal = state.Defense.Active + state.Defense.Active = true + state.Defense.Type = 'Physical' + notifyDescription = state.Defense.Type .. ' Defense' + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + elseif activateField == 'magicaldefense' then + oldVal = state.Defense.Active + state.Defense.Active = true + state.Defense.Type = 'Magical' + notifyDescription = state.Defense.Type .. ' Defense' + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + elseif activateField == 'kite' or toggleField == 'kiting' then + oldVal = state.Kiting + state.Kiting = true + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif activateField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = true + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[cmdParams[1]]) == 'boolean' then + oldVal = state[cmdParams[1]] + state[cmdParams[1]] = true + notifyDescription = cmdParams[1] + reportDescription = cmdParams[1] + elseif job_activate_state then + reportDescription, newVal = job_activate_state(cmdParams[1]) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-GearSwap: Activate: Unknown field ['..activateField..']') + end +end + + +-- Handle cycling through the options for specific vars that we know of. +-- Valid fields: OffenseMode, DefenseMode, WeaponskillMode, IdleMode, RestingMode, CastingMode, PhysicalDefenseMode, MagicalDefenseMode +-- All fields must end in 'Mode' +-- Returns true if a known toggle was handled. Returns false if not. +-- User command format: gs c cycle [field] +function handle_cycle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Cycle parameter failure: field not specified.') + return + end + + -- identifier for the field we're changing + local paramField = cmdParams[1] + local modeField = paramField + local order = (cmdParams[2] and S{'reverse', 'backwards', 'r'}:contains(cmdParams[2]:lower()) and 'backwards') or 'forward' + + if paramField:endswith('mode') or paramField:endswith('Mode') then + -- Remove 'mode' from the end of the string + modeField = paramField:sub(1,#paramField-4) + end + + -- Convert WS to Weaponskill + if modeField == "ws" then + modeField = "weaponskill" + end + + -- Capitalize the field (for use on output display) + modeField = modeField:gsub("%f[%a]%a", string.upper) + + -- Get the options.XXXModes table, and the current state mode for the mode field. + local modeList, currentValue = get_mode_list(modeField) + + if not modeList then + if _global.debug_mode then add_to_chat(123,'Unknown mode : '..modeField..'.') end + return + end + + -- Get the index of the current mode. Index starts at 0 for 'undefined', so that it can increment to 1. + local invertedTable = invert_table(modeList) + local index = 0 + if invertedTable[currentValue] then + index = invertedTable[currentValue] + end + + -- Increment to the next index in the available modes. + if order == 'forward' then + index = index + 1 + if index > #modeList then + index = 1 + end + else + index = index - 1 + if index < 1 then + index = #modeList + end + end + + -- Determine the new mode value based on the index. + local newModeValue = '' + if index and modeList[index] then + newModeValue = modeList[index] + else + newModeValue = 'Normal' + end + + -- And save that to the appropriate state field. + set_option_mode(modeField, newModeValue) + + if job_state_change and newModeValue ~= currentValue then + job_state_change(modeField..'Mode', newModeValue, currentValue) + end + + -- Display what got changed to the user. + add_to_chat(122,modeField..' mode is now '..newModeValue..'.') + handle_update({'auto'}) +end + +-- Function to set various states to specific values directly. +-- User command format: gs c set [field] [value] +function handle_set(cmdParams) + if #cmdParams > 1 then + -- identifier for the field we're setting + local field = cmdParams[1] + local lowerField = field:lower() + local capField = lowerField:gsub("%a", string.upper, 1) + local setField = cmdParams[2] + local reportDescription + local notifyDescription + local fieldDesc + local oldVal + local newVal + + + -- Check if we're dealing with a boolean + if on_off_values:contains(setField:lower()) then + newVal = true_values:contains(setField:lower()) + + if lowerField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = newVal + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif lowerField == 'kite' or lowerField == 'kiting' then + oldVal = state.Kiting + state.Kiting = newVal + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif lowerField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = newVal + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[field]) == 'boolean' then + oldVal = state[field] + state[field] = newVal + notifyDescription = field + reportDescription = field + elseif job_set_state then + reportDescription, newVal = job_set_state(field, newVal) + end + + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + -- Check if we're dealing with some sort of cycle field (ends with 'mode'). + elseif lowerField:endswith('mode') or type(state[capField..'Mode']) == 'string' then + local modeField = lowerField + + -- Remove 'mode' from the end of the string + if modeField:endswith('mode') then + modeField = lowerField:sub(1,#lowerField-4) + end + + -- Convert WS to Weaponskill + if modeField == "ws" then + modeField = "weaponskill" + end + + -- Capitalize the field (for use on output display) + modeField = modeField:gsub("%a", string.upper, 1) + + -- Get the options.XXXModes table, and the current state mode for the mode field. + local modeList + modeList, oldVal = get_mode_list(modeField) + + if not modeList or not table.contains(modeList, setField) then + add_to_chat(123,'Unknown mode value: '..setField..' for '..modeField..' mode.') + return + end + + -- And save that to the appropriate state field. + set_option_mode(modeField, setField) + + -- Notify the job script of the change. + if job_state_change and setField ~= oldVal then + job_state_change(modeField, setField, oldVal) + end + + -- Display what got changed to the user. + add_to_chat(122,modeField..' mode is now '..setField..'.') + -- Or distance (where we may need to get game state info) + elseif lowerField == 'distance' then + if setField then + newVal = tonumber(setField) + if newVal ~= nil then + oldVal = state.MaxWeaponskillDistance + state.MaxWeaponskillDistance = newVal + else + add_to_chat(123,'Invalid distance value: '..tostring(setField)) + return + end + + -- Notify the job script of the change. + if job_state_change and newVal ~= oldVal then + job_state_change('MaxWeaponskillDistance', newVal, oldVal) + end + + add_to_chat(122,'Max weaponskill distance is now '..tostring(newVal)..'.') + else + -- set max weaponskill distance to the current distance the player is from the mob. + -- Get current player distance and use that + add_to_chat(123,'TODO: get player distance.') + end + -- If trying to set a number + elseif tonumber(setField) then + if state[field] and type(state[field]) == 'number' then + oldVal = state[field] + newVal = tonumber(setField) + state[field] = newVal + reportDescription = field + + -- Notify the job script of the change. + if job_state_change and newVal ~= oldVal then + job_state_change(field, newVal, oldVal) + end + elseif state[field] then + add_to_chat(123,'Mote-GearSwap: Set: Attempting to set a numeric value ['..setField..'] in a non-numeric field ['..field..'].') + return + elseif job_set_state then + reportDescription, newVal = job_set_state(field, setField) + end + + if reportDescription then + add_to_chat(122,field..' is now '..tostring(newVal)..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + -- otherwise assume trying to set a text field + else + if lowerField == 'combatform' then + oldVal = state.CombatForm + state.CombatForm = setField + newVal = setField + notifyDescription = 'Combat Form' + reportDescription = 'Combat Form' + elseif lowerField == 'combatweapon' then + oldVal = state.CombatWeapon + state.CombatWeapon = setField + newVal = setField + notifyDescription = 'Combat Weapon' + reportDescription = 'Combat Weapon' + elseif state[field] and type(state[field]) == 'string' then + oldVal = state[field] + state[field] = setField + newVal = setField + notifyDescription = field + reportDescription = field + elseif job_set_state then + reportDescription, newVal = job_set_state(field, setField) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..newVal..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + end + else + if _global.debug_mode then add_to_chat(123,'--handle_set parameter failure: insufficient fields') end + return false + end + + handle_update({'auto'}) + return true +end + + +-- Function to turn off togglable features, or reset values to their defaults. +-- User command format: gs c reset [field] +function handle_reset(cmdParams) + if #cmdParams == 0 then + if _global.debug_mode then add_to_chat(123,'handle_reset: parameter failure: reset type not specified') end + return + end + + resetState = cmdParams[1]:lower() + + if resetState == 'defense' then + state.Defense.Active = false + add_to_chat(122,state.Defense.Type..' defense is now off.') + elseif resetState == 'kite' or resetState == 'kiting' then + state.Kiting = false + add_to_chat(122,'Kiting is now off.') + elseif resetState == 'melee' then + state.OffenseMode = options.OffenseModes[1] + state.DefenseMode = options.DefenseModes[1] + add_to_chat(122,'Melee has been reset to defaults.') + elseif resetState == 'casting' then + state.CastingMode = options.CastingModes[1] + add_to_chat(122,'Casting has been reset to default.') + elseif resetState == 'distance' then + state.MaxWeaponskillDistance = 0 + add_to_chat(122,'Max weaponskill distance limitations have been removed.') + elseif resetState == 'target' then + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + add_to_chat(122,'Adjusting target selection has been turned off.') + elseif resetState == 'all' then + state.Defense.Active = false + state.Defense.PhysicalMode = options.PhysicalDefenseModes[1] + state.Defense.MagicalMode = options.MagicalDefenseModes[1] + state.Kiting = false + state.OffenseMode = options.OffenseModes[1] + state.DefenseMode = options.DefenseModes[1] + state.CastingMode = options.CastingModes[1] + state.IdleMode = options.IdleModes[1] + state.RestingMode = options.RestingModes[1] + state.MaxWeaponskillDistance = 0 + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + mote_vars.show_set = nil + if job_reset then + job_reset(resetState) + end + add_to_chat(122,'Everything has been reset to defaults.') + elseif job_reset then + job_reset(resetState) + else + add_to_chat(123,'handle_reset: unknown state to reset: '..resetState) + return + end + + if job_state_change then + job_state_change('Reset', resetState) + end + + handle_update({'auto'}) +end + + +-- User command format: gs c update [option] +-- Where [option] can be 'user' to display current state. +-- Otherwise, generally refreshes current gear used. +function handle_update(cmdParams) + -- init a new eventArgs + local eventArgs = {handled = false} + + reset_buff_states() + + -- Allow jobs to override this code + if job_update then + job_update(cmdParams, eventArgs) + end + + if not eventArgs.handled then + if handle_equipping_gear then + handle_equipping_gear(player.status) + end + end + + if cmdParams[1] == 'user' then + display_current_state() + end +end + + +-- showset: equip the current TP set for examination. +function handle_show_set(cmdParams) + local showset_type + if cmdParams[1] then + showset_type = cmdParams[1]:lower() + end + + -- If no extra parameters, or 'tp' as a parameter, show the current TP set. + if not showset_type or showset_type == 'tp' then + local meleeGroups = '' + if #classes.CustomMeleeGroups > 0 then + meleeGroups = ' [' + for i = 1,#classes.CustomMeleeGroups do + meleeGroups = meleeGroups..classes.CustomMeleeGroups[i] + end + meleeGroups = meleeGroups..']' + end + + add_to_chat(122,'Showing current TP set: ['..state.OffenseMode..'/'..state.DefenseMode..']'..meleeGroups) + equip(get_melee_set()) + -- If given a param of 'precast', block equipping midcast/aftercast sets + elseif showset_type == 'precast' then + mote_vars.show_set = 'precast' + add_to_chat(122,'GearSwap will now only equip up to precast gear for spells/actions.') + -- If given a param of 'midcast', block equipping aftercast sets + elseif showset_type == 'midcast' then + mote_vars.show_set = 'midcast' + add_to_chat(122,'GearSwap will now only equip up to midcast gear for spells.') + -- If given a param of 'midcast', block equipping aftercast sets + elseif showset_type == 'petmidcast' or showset_type == 'pet_midcast' then + mote_vars.show_set = 'pet_midcast' + add_to_chat(122,'GearSwap will now only equip up to pet midcast gear for spells.') + -- With a parameter of 'off', turn off showset functionality. + elseif showset_type == 'off' then + mote_vars.show_set = nil + add_to_chat(122,'Show Sets is turned off.') + end +end + +-- Minor variation on the GearSwap "gs equip naked" command, that ensures that +-- all slots are enabled before removing gear. +-- Command: "gs c naked" +function handle_naked(cmdParams) + enable('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') + equip(sets.naked) +end + + +------ Utility functions to support self commands. ------ + +-- Function to get the options.XXXModes list and the corresponding state value for the requested field. +function get_mode_list(field) + local modeList = {} + local currentValue = '' + local lowerField = field:lower() + + if type(state[field..'Mode']) == 'string' and type(options[field..'Modes']) == 'table' then + -- Handles: Offense, Defense, Ranged, Casting, Weaponskill, Idle, Resting modes + modeList = options[field..'Modes'] + currentValue = state[field..'Mode'] + elseif lowerField == 'physicaldefense' then + modeList = options.PhysicalDefenseModes + currentValue = state.Defense.PhysicalMode + elseif lowerField == 'magicaldefense' then + modeList = options.MagicalDefenseModes + currentValue = state.Defense.MagicalMode + elseif lowerField == 'pctarget' then + modeList = options.TargetModes + currentValue = state.PCTargetMode + elseif type(state[field..'Mode']) == 'string' and type(options[field..'Modes']) ~= 'table' then + -- naming conflict + add_to_chat(123,'No valid options table for field: '..field) + elseif type(state[field..'Mode']) ~= 'string' and type(options[field..'Modes']) == 'table' then + -- naming conflict + add_to_chat(123,'No valid state string for field: '..field) + elseif job_get_option_modes then + -- Allow job scripts to expand the mode table lists + modeList, currentValue = job_get_option_modes(field) + if not modeList then + add_to_chat(123,'Attempt to acquire options list for unknown state field: '..field) + return nil + end + else + add_to_chat(123,'Attempt to acquire options list for unknown state field: '..field) + return nil + end + + return modeList, currentValue +end + +-- Function to set the appropriate state value for the specified field. +function set_option_mode(field, val) + local lowerField = field:lower() + + if type(state[field..'Mode']) == 'string' then + -- Handles: Offense, Defense, Ranged, Casting, Weaponskill, Idle, Resting modes + state[field..'Mode'] = val + elseif lowerField == 'physicaldefense' then + state.Defense.PhysicalMode = val + elseif lowerField == 'magicaldefense' then + state.Defense.MagicalMode = val + elseif lowerField == 'pctarget' then + state.PCTargetMode = val + elseif job_set_option_mode then + -- Allow job scripts to expand the mode table lists + if not job_set_option_mode(field, val) then + add_to_chat(123,'Attempt to set unknown option field: '..field) + end + else + add_to_chat(123,'Attempt to set unknown option field: '..field) + end +end + + +-- Function to display the current relevant user state when doing an update. +-- Uses display_current_job_state instead if that is defined in the job lua. +function display_current_state() + local eventArgs = {handled = false} + if display_current_job_state then + display_current_job_state(eventArgs) + end + + if not eventArgs.handled then + local defenseString = '' + if state.Defense.Active then + local defMode = state.Defense.PhysicalMode + if state.Defense.Type == 'Magical' then + defMode = state.Defense.MagicalMode + end + + defenseString = 'Defense: '..state.Defense.Type..' '..defMode..', ' + end + + local pcTarget = '' + if state.PCTargetMode ~= 'default' then + pcTarget = ', Target PC: '..state.PCTargetMode + end + + local npcTarget = '' + if state.SelectNPCTargets then + pcTarget = ', Target NPCs' + end + + + add_to_chat(122,'Melee: '..state.OffenseMode..'/'..state.DefenseMode..', WS: '..state.WeaponskillMode..', '..defenseString.. + 'Kiting: '..on_off_names[state.Kiting]..pcTarget..npcTarget) + end + + if mote_vars.show_set then + add_to_chat(122,'Show Sets it currently showing ['..mote_vars.show_set..'] sets. Use "//gs c showset off" to turn it off.') + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Test functions. +------------------------------------------------------------------------------------------------------------------- + +-- A function for testing lua code. Called via "gs c test". +function handle_test(cmdParams) + if user_test then + user_test(cmdParams) + end +end + + + +------------------------------------------------------------------------------------------------------------------- +-- The below table maps text commands to the above handler functions. +------------------------------------------------------------------------------------------------------------------- + +selfCommandMaps = { + ['toggle'] = handle_toggle, + ['activate'] = handle_activate, + ['cycle'] = handle_cycle, + ['set'] = handle_set, + ['reset'] = handle_reset, + ['update'] = handle_update, + ['showset'] = handle_show_set, + ['naked'] = handle_naked, + ['test'] = handle_test} + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua new file mode 100644 index 0000000..7367e85 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua @@ -0,0 +1,297 @@ +------------------------------------------------------------------------------------------------------------------- +-- Utility include for applying and tracking Treasure Hunter effects. +-- +-- Include this if you want a means of applying TH on the first contact +-- with a mob, then resume using normal gear. +-- Thf also has modes to use TH gear for SATA and for keeping it on fulltime. +-- +-- Command: include('Mote-TreasureHunter') +-- Place this in your job_setup() function, or user_setup() function if using +-- a sidecar file, or get_sets() function if your job file isn't based +-- on my includes. +-- Be sure to define your own sets.TreasureHunter gear set after the include. +-- If using a job file based on my includes, simply place it in the +-- standard init_gear_sets() function. +-- +-- If you define TH gear sets for common actions (eg: Provoke, Box Step, etc), +-- then make sure they are accounted for in a th_action_check function +-- (signature: th_action_check(category, param)) in the job file. It's passed +-- category and param value for actions the user takes, and if it returns true, +-- that means that it's considered a valid tagging action. +-- +-- If using this in a job file that isn't based on my includes, you must +-- handle cycling the options values on your own, unless you also include +-- Mote-SelfCommands. +-- +-- The job file must handle the 'update' self-command (gs c update auto). +-- This is automatically handled if using my includes, but must be ensured +-- if being used with a user-built job file. +-- When called, it merely needs to equip standard melee gear for the current +-- configuration. +-- +-- Create a macro or keybind to cycle the Treasure Mode value: +-- gs c cycle TreasureMode +------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------- +-- Setup vars and events when first running the include. +------------------------------------------------------------------------------------------------------------------- + +-- Ensure base tables are defined +options = options or {} +state = state or {} +info = info or {} + +-- TH mode handling +if player.main_job == 'THF' then + options.TreasureModes = {'None','Tag','SATA','Fulltime'} + state.TreasureMode = 'Tag' +else + options.TreasureModes = {'None','Tag'} + state.TreasureMode = 'None' +end + +-- Tracking vars for TH. +info.tagged_mobs = T{} +info.last_player_target_index = 0 +state.th_gear_is_locked = false + +-- Required gear set. Expand this in the job file when defining sets. +sets.TreasureHunter = {} + +-- Event registration is done at the bottom of this file. + + +------------------------------------------------------------------------------------------------------------------- +-- User-callable functions for TH handling utility. +------------------------------------------------------------------------------------------------------------------- + +-- Can call to force a status refresh. +-- Also displays the current tagged mob table if in debug mode. +function th_update(cmdParams, eventArgs) + if (cmdParams and cmdParams[1] == 'user') or not cmdParams then + TH_for_first_hit() + + if _settings.debug_mode then + print_set(info.tagged_mobs, 'Tagged mobs') + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Local functions to support TH handling. +------------------------------------------------------------------------------------------------------------------- + +-- Set locked TH flag to true, and disable relevant gear slots. +function lock_TH() + state.th_gear_is_locked = true + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + disable(slots) +end + + +-- Set locked TH flag to false, and enable relevant gear slots. +function unlock_TH() + state.th_gear_is_locked = false + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + enable(slots) + send_command('gs c update auto') +end + + +-- For any active TH mode, if we haven't already tagged this target, equip TH gear and lock slots until we manage to hit it. +function TH_for_first_hit() + if player.status == 'Engaged' and state.TreasureMode ~= 'None' then + if not info.tagged_mobs[player.target.id] then + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..tostring(player.target.id)..'.') end + equip(sets.TreasureHunter) + lock_TH() + elseif state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'Target '..player.target.id..' has been tagged. Unlocking.') end + unlock_TH() + else + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..player.target.id..'. Target has already been tagged.') end + end + else + unlock_TH() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event handlers to allow tracking TH application. +------------------------------------------------------------------------------------------------------------------- + +-- On engaging a mob, attempt to add TH gear. For any other status change, unlock TH gear slots. +function on_status_change_for_th(new_status_id, old_status_id) + if gearswap.gearswap_disabled or T{2,3,4}:contains(old_status_id) or T{2,3,4}:contains(new_status_id) then return end + + local new_status = gearswap.res.statuses[new_status_id].english + local old_status = gearswap.res.statuses[old_status_id].english + + if new_status == 'Engaged' then + if _settings.debug_mode then add_to_chat(123,'Engaging '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + elseif old_status == 'Engaged' then + if _settings.debug_mode and state.th_gear_is_locked then add_to_chat(123,'Disengaging. Unlocking TH.') end + info.last_player_target_index = 0 + unlock_TH() + end +end + + +-- On changing targets, attempt to add TH gear. +function on_target_change_for_th(new_index, old_index) + -- Only care about changing targets while we're engaged, either manually or via current target death. + if player.status == 'Engaged' then + -- If the current player.target is the same as the new mob then we're actually + -- engaged with it. + -- If it's different than the last known mob, then we've actually changed targets. + if player.target.index == new_index and new_index ~= info.last_player_target_index then + if _settings.debug_mode then add_to_chat(123,'Changing target to '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + end + end +end + + +-- On any action event, mark mobs that we tag with TH. Also, update the last time tagged mobs were acted on. +function on_action_for_th(action) + --add_to_chat(123,'cat='..action.category..',param='..action.param) + -- If player takes action, adjust TH tagging information + if state.TreasureMode ~= 'None' then + if action.actor_id == player.id then + -- category == 1=melee, 2=ranged, 3=weaponskill, 4=spell, 6=job ability, 14=unblinkable JA + if state.TreasureMode == 'Fulltime' or + (state.TreasureMode == 'SATA' and (action.category == 1 or ((state.Buff['Sneak Attack'] or state.Buff['Trick Attack']) and action.category == 3))) or + (state.TreasureMode == 'Tag' and action.category == 1 and state.th_gear_is_locked) or -- Tagging with a melee hit + (th_action_check and th_action_check(action.category, action.param)) -- Any user-specified tagging actions + then + for index,target in pairs(action.targets) do + if not info.tagged_mobs[target.id] and _settings.debug_mode then + add_to_chat(123,'Mob '..target.id..' hit. Adding to tagged mobs table.') + end + info.tagged_mobs[target.id] = os.time() + end + + if state.th_gear_is_locked then + unlock_TH() + end + end + elseif info.tagged_mobs[action.actor_id] then + -- If mob acts, keep an update of last action time for TH bookkeeping + info.tagged_mobs[action.actor_id] = os.time() + else + -- If anyone else acts, check if any of the targets are our tagged mobs + for index,target in pairs(action.targets) do + if info.tagged_mobs[target.id] then + info.tagged_mobs[target.id] = os.time() + end + end + end + end + + cleanup_tagged_mobs() +end + + +-- Need to use this event handler to listen for deaths in case Battlemod is loaded, +-- because Battlemod blocks the 'action message' event. +-- +-- This function removes mobs from our tracking table when they die. +function on_incoming_chunk_for_th(id, data, modified, injected, blocked) + if id == 0x29 then + local target_id = data:unpack('I',0x09) + local message_id = data:unpack('H',0x19)%32768 + + -- Remove mobs that die from our tagged mobs list. + if info.tagged_mobs[target_id] then + -- 6 == actor defeats target + -- 20 == target falls to the ground + if message_id == 6 or message_id == 20 then + if _settings.debug_mode then add_to_chat(123,'Mob '..target_id..' died. Removing from tagged mobs table.') end + info.tagged_mobs[target_id] = nil + end + end + end +end + + +-- Clear out the entire tagged mobs table when zoning. +function on_zone_change_for_th(new_zone, old_zone) + if _settings.debug_mode then add_to_chat(123,'Zoning. Clearing tagged mobs table.') end + info.tagged_mobs:clear() +end + + +-- Save the existing function, if it exists, and call it after our own handling. +if job_state_change then + job_state_change_via_th = job_state_change +end + + +-- Called if we change any user state fields. +function job_state_change(stateField, newValue, oldValue) + if stateField == 'TreasureMode' then + if newValue == 'None' and state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'TH Mode set to None. Unlocking gear.') end + unlock_TH() + elseif oldValue == 'None' then + TH_for_first_hit() + end + end + + if job_state_change_via_th then + job_state_change_via_th(stateField, newValue, oldValue) + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Extra utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Remove mobs that we've marked as tagged with TH if we haven't seen any activity from or on them +-- for over 3 minutes. This is to handle deagros, player deaths, or other random stuff where the +-- mob is lost, but doesn't die. +function cleanup_tagged_mobs() + -- If it's been more than 3 minutes since an action on or by a tagged mob, + -- remove them from the tagged mobs list. + local current_time = os.time() + local remove_mobs = S{} + -- Search list and flag old entries. + for target_id,action_time in pairs(info.tagged_mobs) do + local time_since_last_action = current_time - action_time + if time_since_last_action > 180 then + remove_mobs:add(target_id) + if _settings.debug_mode then add_to_chat(123,'Over 3 minutes since last action on mob '..target_id..'. Removing from tagged mobs list.') end + end + end + -- Clean out mobs flagged for removal. + for mob_id,_ in pairs(remove_mobs) do + info.tagged_mobs[mob_id] = nil + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event function registration calls. +-- Can call these now that the above functions have been defined. +------------------------------------------------------------------------------------------------------------------- + +-- Register events to allow us to manage TH application. +windower.register_event('status change', on_status_change_for_th) +windower.register_event('target change', on_target_change_for_th) +windower.raw_register_event('action', on_action_for_th) +windower.raw_register_event('incoming chunk', on_incoming_chunk_for_th) +windower.raw_register_event('zone change', on_zone_change_for_th) + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Utility.lua b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Utility.lua new file mode 100644 index 0000000..8cf3c49 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-Utility.lua @@ -0,0 +1,672 @@ +------------------------------------------------------------------------------------------------------------------- +-- General utility functions that can be used by any job files. +-- Outside the scope of what the main include file deals with. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Buff utility functions. +------------------------------------------------------------------------------------------------------------------- + +local cancel_spells_to_check = S{'Sneak', 'Stoneskin', 'Spectral Jig', 'Trance', 'Monomi: Ichi', 'Utsusemi: Ichi'} +local cancel_types_to_check = S{'Waltz', 'Samba'} + +-- Function to cancel buffs if they'd conflict with using the spell you're attempting. +-- Requirement: Must have Cancel addon installed and loaded for this to work. +function cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + if cancel_spells_to_check:contains(spell.english) or cancel_types_to_check:contains(spell.type) then + if spell.action_type == 'Ability' then + local abil_recasts = windower.ffxi.get_ability_recasts() + if abil_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Ability waiting on recast.') + eventArgs.cancel = true + return + end + elseif spell.action_type == 'Magic' then + local spell_recasts = windower.ffxi.get_spell_recasts() + if spell_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Spell waiting on recast.') + eventArgs.cancel = true + return + end + end + + if spell.english == 'Spectral Jig' and buffactive.sneak then + cast_delay(0.2) + send_command('cancel sneak') + elseif spell.english == 'Sneak' and spell.target.type == 'SELF' and buffactive.sneak then + send_command('cancel sneak') + elseif spell.english == ('Stoneskin') then + send_command('@wait 1.0;cancel stoneskin') + elseif spell.english:startswith('Monomi') then + send_command('@wait 1.7;cancel sneak') + elseif spell.english == 'Utsusemi: Ichi' then + send_command('@wait 1.7;cancel copy image*') + elseif (spell.english == 'Trance' or spell.type=='Waltz') and buffactive['saber dance'] then + cast_delay(0.2) + send_command('cancel saber dance') + elseif spell.type=='Samba' and buffactive['fan dance'] then + cast_delay(0.2) + send_command('cancel fan dance') + end + end +end + + +-- Some mythics have special durations for level 1 and 2 aftermaths +local special_aftermath_mythics = S{'Tizona', 'Kenkonken', 'Murgleis', 'Yagrush', 'Carnwenhan', 'Nirvana', 'Tupsimati', 'Idris'} + +-- Call from job_precast() to setup aftermath information for custom timers. +function custom_aftermath_timers_precast(spell) + if spell.type == 'WeaponSkill' then + info.aftermath = {} + + local relic_ws = data.weaponskills.relic[player.equipment.main] or data.weaponskills.relic[player.equipment.range] + local mythic_ws = data.weaponskills.mythic[player.equipment.main] or data.weaponskills.mythic[player.equipment.range] + local empy_ws = data.weaponskills.empyrean[player.equipment.main] or data.weaponskills.empyrean[player.equipment.range] + + if not relic_ws and not mythic_ws and not empy_ws then + return + end + + info.aftermath.weaponskill = spell.english + info.aftermath.duration = 0 + + info.aftermath.level = math.floor(player.tp / 1000) + if info.aftermath.level == 0 then + info.aftermath.level = 1 + end + + if spell.english == relic_ws then + info.aftermath.duration = math.floor(0.2 * player.tp) + if info.aftermath.duration < 20 then + info.aftermath.duration = 20 + end + elseif spell.english == empy_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- duration is based on aftermath level + info.aftermath.duration = 30 * info.aftermath.level + elseif spell.english == mythic_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- Assume mythic is lvl 80 or higher, for duration + + if info.aftermath.level == 1 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 90 + elseif info.aftermath.level == 2 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 120 + else + info.aftermath.duration = 180 + end + end + end +end + + +-- Call from job_aftercast() to create the custom aftermath timer. +function custom_aftermath_timers_aftercast(spell) + if not spell.interrupted and spell.type == 'WeaponSkill' and + info.aftermath and info.aftermath.weaponskill == spell.english and info.aftermath.duration > 0 then + + local aftermath_name = 'Aftermath: Lv.'..tostring(info.aftermath.level) + send_command('timers d "Aftermath: Lv.1"') + send_command('timers d "Aftermath: Lv.2"') + send_command('timers d "Aftermath: Lv.3"') + send_command('timers c "'..aftermath_name..'" '..tostring(info.aftermath.duration)..' down abilities/00027.png') + + info.aftermath = {} + end +end + + +-- Function to reset state.Buff values. +function reset_buff_states() + if state.Buff then + for buff,present in pairs(state.Buff) do + state.Buff[buff] = buffactive[buff] or false + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for changing spells and target types in an automatic manner. +------------------------------------------------------------------------------------------------------------------- + +local waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Curing Waltz IV'] = 650, ['Curing Waltz V'] = 800} + +-- Utility function for automatically adjusting the waltz spell being used to match HP needs and TP limits. +-- Handle spell changes before attempting any precast stuff. +function refine_waltz(spell, action, spellMap, eventArgs) + if spell.type ~= 'Waltz' then + return + end + + -- Don't modify anything for Healing Waltz or Divine Waltzes + if spell.english == "Healing Waltz" or spell.english == "Divine Waltz" or spell.english == "Divine Waltz II" then + return + end + + local newWaltz = spell.english + local waltzID + + local missingHP + + -- If curing ourself, get our exact missing HP + if spell.target.type == "SELF" then + missingHP = player.max_hp - player.hp + -- If curing someone in our alliance, we can estimate their missing HP + elseif spell.target.isallymember then + local target = find_player_in_alliance(spell.target.name) + local est_max_hp = target.hp / (target.hpp/100) + missingHP = math.floor(est_max_hp - target.hp) + end + + -- If we have an estimated missing HP value, we can adjust the preferred tier used. + if missingHP ~= nil then + if player.main_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 200 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 600 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + elseif missingHP < 1100 then + newWaltz = 'Curing Waltz III' + waltzID = 192 + elseif missingHP < 1500 then + newWaltz = 'Curing Waltz IV' + waltzID = 193 + else + newWaltz = 'Curing Waltz V' + waltzID = 311 + end + elseif player.sub_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 150 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 300 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + else + newWaltz = 'Curing Waltz III' + waltzID = 192 + end + else + -- Not dnc main or sub; bail out + return + end + end + + local tpCost = waltz_tp_cost[newWaltz] + + local downgrade + + -- Downgrade the spell to what we can afford + if player.tp < tpCost and not buffactive.trance then + --[[ Costs: + Curing Waltz: 200 TP + Curing Waltz II: 350 TP + Curing Waltz III: 500 TP + Curing Waltz IV: 650 TP + Curing Waltz V: 800 TP + Divine Waltz: 400 TP + Divine Waltz II: 800 TP + --]] + + if player.tp < 200 then + add_to_chat(122, 'Insufficient TP ['..tostring(player.tp)..']. Cancelling.') + eventArgs.cancel = true + return + elseif player.tp < 350 then + newWaltz = 'Curing Waltz' + elseif player.tp < 500 then + newWaltz = 'Curing Waltz II' + elseif player.tp < 650 then + newWaltz = 'Curing Waltz III' + elseif player.tp < 800 then + newWaltz = 'Curing Waltz IV' + end + + downgrade = 'Insufficient TP ['..tostring(player.tp)..']. Downgrading to '..newWaltz..'.' + end + + + if newWaltz ~= spell.english then + send_command('@input /ja "'..newWaltz..'" '..tostring(spell.target.raw)) + if downgrade then + add_to_chat(122, downgrade) + end + eventArgs.cancel = true + return + end + + if missingHP and missingHP > 0 then + add_to_chat(122,'Trying to cure '..tostring(missingHP)..' HP using '..newWaltz..'.') + end +end + + +-- Function to allow for automatic adjustment of the spell target type based on preferences. +function auto_change_target(spell, spellMap) + -- Don't adjust targetting for explicitly named targets + if not spell.target.raw:startswith('<') then + return + end + + -- Do not modify target for spells where we get <lastst> or <me>. + if spell.target.raw == ('<lastst>') or spell.target.raw == ('<me>') then + return + end + + -- init a new eventArgs with current values + local eventArgs = {handled = false, PCTargetMode = state.PCTargetMode, SelectNPCTargets = state.SelectNPCTargets} + + -- Allow the job to do custom handling, or override the default values. + -- They can completely handle it, or set one of the secondary eventArgs vars to selectively + -- override the default state vars. + if job_auto_change_target then + job_auto_change_target(spell, action, spellMap, eventArgs) + end + + -- If the job handled it, we're done. + if eventArgs.handled then + return + end + + local pcTargetMode = eventArgs.PCTargetMode + local selectNPCTargets = eventArgs.SelectNPCTargets + + + local validPlayers = S{'Self', 'Player', 'Party', 'Ally', 'NPC'} + + local intersection = spell.targets * validPlayers + local canUseOnPlayer = not intersection:empty() + + local newTarget + + -- For spells that we can cast on players: + if canUseOnPlayer and pcTargetMode ~= 'default' then + -- Do not adjust targetting for player-targettable spells where the target was <t> + if spell.target.raw ~= ('<t>') then + if pcTargetMode == 'stal' then + -- Use <stal> if possible, otherwise fall back to <stpt>. + if spell.targets.Ally then + newTarget = '<stal>' + elseif spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpt' then + -- Even ally-possible spells are limited to the current party. + if spell.targets.Ally or spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpc' then + -- If it's anything other than a self-only spell, can change to <stpc>. + if spell.targets.Player or spell.targets.Party or spell.targets.Ally or spell.targets.NPC then + newTarget = '<stpc>' + end + end + end + -- For spells that can be used on enemies: + elseif spell.targets and spell.targets.Enemy and selectNPCTargets then + -- Note: this means macros should be written for <t>, and it will change to <stnpc> + -- if the flag is set. It won't change <stnpc> back to <t>. + newTarget = '<stnpc>' + end + + -- If a new target was selected and is different from the original, call the change function. + if newTarget and newTarget ~= spell.target.raw then + change_target(newTarget) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Environment utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Function to get the current weather intensity: 0 for none, 1 for single weather, 2 for double weather. +function get_weather_intensity() + return gearswap.res.weather[world.weather_id].intensity +end + + +-- Returns true if you're in a party solely comprised of Trust NPCs. +-- TODO: Do we need a check to see if we're in a party partly comprised of Trust NPCs? +function is_trust_party() + -- Check if we're solo + if party.count == 1 then + return false + end + + -- Can call a max of 3 Trust NPCs, so parties larger than that are out. + if party.count > 4 then + return false + end + + -- If we're in an alliance, can't be a Trust party. + if alliance[2].count > 0 or alliance[3].count > 0 then + return false + end + + -- Check that, for each party position aside from our own, the party + -- member has one of the Trust NPC names, and that those party members + -- are flagged is_npc. + for i = 2,4 do + if party[i] then + if not npcs.Trust:contains(party[i].name) then + return false + end + if party[i].mob and party[i].mob.is_npc == false then + return false + end + end + end + + -- If it didn't fail any of the above checks, return true. + return true +end + + +-- Call these function with a list of equipment slots to check ('head', 'neck', 'body', etc) +-- Returns true if any of the specified slots are currently encumbered. +-- Returns false if all specified slots are unencumbered. +function is_encumbered(...) + local check_list = {...} + -- Compensate for people passing a table instead of a series of strings. + if type(check_list[1]) == 'table' then + check_list = check_list[1] + end + local check_set = S(check_list) + + for slot_id,slot_name in pairs(gearswap.default_slot_map) do + if check_set:contains(slot_name) then + if gearswap.encumbrance_table[slot_id] then + return true + end + end + end + + return false +end + +------------------------------------------------------------------------------------------------------------------- +-- Elemental gear utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- General handler function to set all the elemental gear for an action. +function set_elemental_gear(spell) + set_elemental_gorget_belt(spell) + set_elemental_obi_cape_ring(spell) + set_elemental_staff(spell) +end + + +-- Set the name field of the predefined gear vars for gorgets and belts, for the specified weaponskill. +function set_elemental_gorget_belt(spell) + if spell.type ~= 'WeaponSkill' then + return + end + + -- Get the union of all the skillchain elements for the weaponskill + local weaponskill_elements = S{}: + union(skillchain_elements[spell.skillchain_a]): + union(skillchain_elements[spell.skillchain_b]): + union(skillchain_elements[spell.skillchain_c]) + + gear.ElementalGorget.name = get_elemental_item_name("gorget", weaponskill_elements) or gear.default.weaponskill_neck or "" + gear.ElementalBelt.name = get_elemental_item_name("belt", weaponskill_elements) or gear.default.weaponskill_waist or "" +end + + +-- Function to get an appropriate obi/cape/ring for the current action. +function set_elemental_obi_cape_ring(spell) + if spell.element == 'None' then + return + end + + local world_elements = S{world.day_element} + if world.weather_element ~= 'None' then + world_elements:add(world.weather_element) + end + + local obi_name = get_elemental_item_name("obi", S{spell.element}, world_elements) + gear.ElementalObi.name = obi_name or gear.default.obi_waist or "" + + if obi_name then + if player.inventory['Twilight Cape'] or player.wardrobe['Twilight Cape'] then + gear.ElementalCape.name = "Twilight Cape" + end + if (player.inventory['Zodiac Ring'] or player.wardrobe['Zodiac Ring']) and spell.english ~= 'Impact' and + not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then + gear.ElementalRing.name = "Zodiac Ring" + end + else + gear.ElementalCape.name = gear.default.obi_back + gear.ElementalRing.name = gear.default.obi_ring + end +end + + +-- Function to get the appropriate fast cast and/or recast staves for the current spell. +function set_elemental_staff(spell) + if spell.action_type ~= 'Magic' then + return + end + + gear.FastcastStaff.name = get_elemental_item_name("fastcast_staff", S{spell.element}) or gear.default.fastcast_staff or "" + gear.RecastStaff.name = get_elemental_item_name("recast_staff", S{spell.element}) or gear.default.recast_staff or "" +end + + +-- Gets the name of an elementally-aligned piece of gear within the player's +-- inventory that matches the conditions set in the parameters. +-- +-- item_type: Type of item as specified in the elemental_map mappings. +-- EG: gorget, belt, obi, fastcast_staff, recast_staff +-- +-- valid_elements: Elements that are valid for the action being taken. +-- IE: Weaponskill skillchain properties, or spell element. +-- +-- restricted_to_elements: Secondary elemental restriction that limits +-- whether the item check can be considered valid. +-- EG: Day or weather elements that have to match the spell element being queried. +-- +-- Returns: Nil if no match was found (either due to elemental restrictions, +-- or the gear isn't in the player inventory), or the name of the piece of +-- gear that matches the query. +function get_elemental_item_name(item_type, valid_elements, restricted_to_elements) + local potential_elements = restricted_to_elements or elements.list + local item_map = elements[item_type:lower()..'_of'] + + for element in (potential_elements.it or it)(potential_elements) do + if valid_elements:contains(element) and (player.inventory[item_map[element]] or player.wardrobe[item_map[element]]) then + return item_map[element] + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Function to easily change to a given macro set or book. Book value is optional. +------------------------------------------------------------------------------------------------------------------- + +function set_macro_page(set,book) + if not tonumber(set) then + add_to_chat(123,'Error setting macro page: Set is not a valid number ('..tostring(set)..').') + return + end + if set < 1 or set > 10 then + add_to_chat(123,'Error setting macro page: Macro set ('..tostring(set)..') must be between 1 and 10.') + return + end + + if book then + if not tonumber(book) then + add_to_chat(123,'Error setting macro page: book is not a valid number ('..tostring(book)..').') + return + end + if book < 1 or book > 20 then + add_to_chat(123,'Error setting macro page: Macro book ('..tostring(book)..') must be between 1 and 20.') + return + end + send_command('@input /macro book '..tostring(book)..';wait .1;input /macro set '..tostring(set)) + else + send_command('@input /macro set '..tostring(set)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for including local user files. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to load user gear files in place of default gear sets. +-- Return true if one exists and was loaded. +function load_sidecar(job) + if not job then return false end + + -- filename format example for user-local files: whm_gear.lua, or playername_whm_gear.lua + local filenames = {player.name..'_'..job..'_gear.lua', job..'_gear.lua', + 'gear/'..player.name..'_'..job..'_gear.lua', 'gear/'..job..'_gear.lua', + 'gear/'..player.name..'_'..job..'.lua', 'gear/'..job..'.lua'} + return optional_include(filenames) +end + +-- Attempt to include user-globals. Return true if it exists and was loaded. +function load_user_globals() + local filenames = {player.name..'-globals.lua', 'user-globals.lua'} + return optional_include(filenames) +end + +-- Optional version of include(). If file does not exist, does not +-- attempt to load, and does not throw an error. +-- filenames takes an array of possible file names to include and checks +-- each one. +function optional_include(filenames) + for _,v in pairs(filenames) do + local path = gearswap.pathsearch({v}) + if path then + include(v) + return true + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for vars or other data manipulation. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to locate a specified name within the current alliance. +function find_player_in_alliance(name) + for party_index,ally_party in ipairs(alliance) do + for player_index,_player in ipairs(ally_party) do + if _player.name == name then + return _player + end + end + end +end + + +-- buff_set is a set of buffs in a library table (any of S{}, T{} or L{}). +-- This function checks if any of those buffs are present on the player. +function has_any_buff_of(buff_set) + return buff_set:any( + -- Returns true if any buff from buff set that is sent to this function returns true: + function (b) return buffactive[b] end + ) +end + + +-- Invert a table such that the keys are values and the values are keys. +-- Use this to look up the index value of a given entry. +function invert_table(t) + if t == nil then error('Attempting to invert table, received nil.', 2) end + + local i={} + for k,v in pairs(t) do + i[v] = k + end + return i +end + + +-- Gets sub-tables based on baseSet from the string str that may be in dot form +-- (eg: baseSet=sets, str='precast.FC', this returns the table sets.precast.FC). +function get_expanded_set(baseSet, str) + local cur = baseSet + for i in str:gmatch("[^.]+") do + if cur then + cur = cur[i] + end + end + + return cur +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions data and event tracking. +------------------------------------------------------------------------------------------------------------------- + +-- This is a function that can be attached to a registered event for 'time change'. +-- It will send a call to the update() function if the time period changes. +-- It will also call job_time_change when any of the specific time class values have changed. +-- To activate this in your job lua, add this line to your user_setup function: +-- windower.register_event('time change', time_change) +-- +-- Variables it sets: classes.Daytime, and classes.DuskToDawn. They are set to true +-- if their respective descriptors are true, or false otherwise. +function time_change(new_time, old_time) + local was_daytime = classes.Daytime + local was_dusktime = classes.DuskToDawn + + if new_time >= 6*60 and new_time < 18*60 then + classes.Daytime = true + else + classes.Daytime = false + end + + if new_time >= 17*60 or new_time < 7*60 then + classes.DuskToDawn = true + else + classes.DuskToDawn = false + end + + if was_daytime ~= classes.Daytime or was_dusktime ~= classes.DuskToDawn then + if job_time_change then + job_time_change(new_time, old_time) + end + + handle_update({'auto'}) + end +end + + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-documentation.txt b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-documentation.txt new file mode 100644 index 0000000..8f17241 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/Mote-documentation.txt @@ -0,0 +1 @@ +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation on the usage of these include files.
\ No newline at end of file diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/README.md b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/README.md new file mode 100644 index 0000000..9fd1351 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/libs/rev1/README.md @@ -0,0 +1,6 @@ +Mote-libs +========= + +These are the Mote-Include library files for GearSwap. + +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation. |