diff options
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/Yush')
-rw-r--r-- | Data/BuiltIn/Libraries/lua-addons/addons/Yush/ReadMe.md | 177 | ||||
-rw-r--r-- | Data/BuiltIn/Libraries/lua-addons/addons/Yush/Yush.lua | 422 |
2 files changed, 599 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/Yush/ReadMe.md b/Data/BuiltIn/Libraries/lua-addons/addons/Yush/ReadMe.md new file mode 100644 index 0000000..3a2818a --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/Yush/ReadMe.md @@ -0,0 +1,177 @@ +# Yush + +A portable macro engine based on customizable Lua files. Triggers faster than in-game macros and allow a significantly higher number of key combinations. + +### Usage + +This addon has no commands, it only works with custom Lua user files. Upon load, login or job change it tries to load one of the following files in the specified order: + +* `P:/ath/to/Windower/addons/Yush/data/Name_MAIN_SUB.lua` +* `P:/ath/to/Windower/addons/Yush/data/Name_MAIN.lua` +* `P:/ath/to/Windower/addons/Yush/data/Name.lua` +* `P:/ath/to/Windower/addons/Yush/data/binds.lua` + +The file needs to return a table. The table is a key -> action mapping, where the key is a combination of keys to press (denoted by the `+` sign) and the action can either be a Windower command or another table. If it's another table, it will open that table and new keys will be looked up in that table. + +To go back to the base level, press the button that has been defined in the `data/settings.xml` file as `ResetKey`. To go back only one level, press the button that has been defined in the same file as `BackKey`. They default to `` ` `` and `Backspace` respectively. + +### Settings + +**ResetKey** + +The key which resets the current macro set to the root set (the same that is active when the file is loaded). + +**BackKey** + +The key which resets the current macro set to the previous set. + +**Verbose** + +If true, will display the current macro set you are in. The name it displays is the same name it has in the file it loads. + +**VerboseOutput** + +Determines where the current macro set will be displayed (only effective if the *Verbose* setting is `true`). The following options are available: +* **Chat**: Will display the current macro set in the FFXI chat log. +* **Console**: Will display the current macro set in the Windower console. +* **Text** (**default**): Will display the current macro set in a text box. + +**Label** + +The properties of the text object holding the current macro set name, if *Verbose* is enabled and *VerboseOutput* set to `Text`. + +### Commands + +``` +yush reset +``` + +Resets the current macro set to the root set (the same that is active when the file is loaded). + +``` +yush back +``` + +Resets the current macro set to the previous set. + +``` +yush press [keys...] +``` + +Simulates a macro key press. This has no effect outside of *Yush* macros and is only there so you can set up commands to simulate *Yush* macro changes. + +``` +yush set <BackKey|ResetKey|Verbose> [value] +``` + +Sets the corresponding settings key to the provided value and saves it for the current character. If no value is provided, it displays the current settings. + +``` +yush save +``` + +Saves the current character's settings for all characters. + +### Includes + +Yush supports inclusion of base files, in case certain jobs share a macro structure. + +If you define a table `WAR` in a file called `WAR-include.lua` that looks like this: +```lua +WAR = { + ['Ctrl+1'] = 'input /ja "Berserk" <me>', + ['Ctrl+2'] = 'input /ja "Warcry" <me>', +} +``` + +You can make use of that file by including it in another file as follows: +```lua +include('WAR-include.lua') +``` + +It's even possible to define tables in multiple files. The order in which they are included is the order that entries will be overwritten in. So if you define a `WAR` table in both `WAR-include.lua` as well as the file you're including it in (`Arcon_THF.lua` in this example), the table would contain entries from both files without any necessary functions or special syntax, where duplicate entries from `Arcon_THF.lua` would take priority. Simply define the table twice and values will be overwritten in the order they appear in in the file. + +Following is another example. This piece is from `WAR-include.lua`: +```lua +WAR = { + ['Ctrl+1'] = 'input /ja "Berserk" <me>', + ['Ctrl+2'] = 'input /ja "Warcry" <me>', +} +``` + +This is from `Arcon_THF.lua`: +```lua +include('WAR-include.lua') + +WAR = { + ['Ctrl+2'] = 'input /ja "Aggressor" <me>', +} +``` + +The result would be *Berserk* on `Ctrl+1` and *Aggressor* on `Ctrl+2`, since the include came first and defined *Warcry* on `Ctrl+2`, but then it was overwritten by the *Aggressor* definition below. + +### Logic + +Yush supports the full use of the Lua language, as well as all Windower API functions and most Lua libraries (possibly all, but they weren't all tested). For example, in the `Arcon_THF.lua` file we can disambiguate which macros to include depending on the subjob: + +```lua +local sub = windower.ffxi.get_player().sub_job +if sub == 'WAR' then + include('WAR-include.lua') +elseif sub == 'DNC' then + include('DNC-include.lua') +end +``` + +### Example + +This is what an example file called `Arcon_THF.lua` in the addon's `data` folder would look like: + +```lua +WAR = { + ['Ctrl+2'] = 'input /ja "Provoke" <me>', + ['Ctrl+3'] = 'input /ja "Warcry" <me>', + ['Ctrl+4'] = 'input /ja "Aggressor" <me>', + ['Ctrl+5'] = 'input /ja "Berserk" <me>', + ['Alt+2'] = 'input /ja "Defender" <me>', +} + +JA = { + ['Ctrl+1'] = 'input /ja "Perfect Dodge" <me>', + ['Ctrl+2'] = 'input /ja "Sneak Attack" <me>', + ['Ctrl+3'] = 'input /ja "Trick Attack" <me>', + ['Ctrl+4'] = 'input /ja "Bully" <me>', + ['Ctrl+5'] = 'input /ja "Hide" <me>', + ['Alt+2'] = WAR, -- Goes to WAR sub table + ['Alt+4'] = 'input /ja "Collaborator" <stpt>', + ['Alt+3'] = 'input /ja "Flee" <me>', +} + +Magic = { + ['Ctrl+2'] = 'input /ma "Utsusemi: Ichi" <me>', + ['Ctrl+3'] = 'input /ma "Utsusemi: Ni" <me>', + ['Alt+2'] = 'input /ma "Monomi: Ichi" <me>', + ['Alt+3'] = 'input /ma "Tonko: Ni" <me>', +} + +WS = { + ['Ctrl+2'] = 'input /ja "Assassin\'s Charge" <me>', + ['Ctrl+3'] = 'input /ws "Aeolian Edge" <t>', + ['Alt+2'] = 'input /ws "Exenterator" <t>', + ['Alt+3'] = 'input /ws "Mercy Stroke" <t>', + ['Alt+4'] = 'input /ws "Evisceration" <t>', +} + +return { + ['Ctrl+1'] = 'input /ja "Perfect Dodge" <me>', + ['Ctrl+2'] = 'autoset', -- Custom alias, equips current idle set according to variables + ['Ctrl+3'] = 'set Regen', -- Custom alias, equips Regen set + ['Ctrl+4'] = 'set Magical', -- Custom alias, equips PDT set + ['Ctrl+5'] = 'set Physical', -- Custom alias, equips MDT set + ['Ctrl+9'] = 'var treasurehunter nil; autoset', + ['Ctrl+0'] = 'var treasurehunter TreasureHunter; autoset', + ['Alt+2'] = JA, -- Goes to JA sub table + ['Alt+3'] = Magic, -- Goes to Magic sub table + ['Alt+5'] = WS, -- Goes to WS sub table +} +``` diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/Yush/Yush.lua b/Data/BuiltIn/Libraries/lua-addons/addons/Yush/Yush.lua new file mode 100644 index 0000000..0bee580 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/Yush/Yush.lua @@ -0,0 +1,422 @@ +_addon.author = 'Arcon' +_addon.version = '2.2.0.0' +_addon.language = 'English' +_addon.command = 'yush' + +require('luau') +require('logger') +texts = require('texts') + +_innerG = {} +for k, v in pairs(_G) do + rawset(_innerG, k, v) +end +_innerG._innerG = nil +_innerG._G = _innerG +_innerG._binds = {} +_innerG._names = {} + +_innerG.include = function(path) + local full_path = '%sdata/%s':format(windower.addon_path, path) + + local file = loadfile(full_path) + if not file then + warning('Include file %s not found.':format(path)) + return + end + + setfenv(file, _innerG) + file() +end + +setmetatable(_innerG, { + __index = function(g, k) + local t = rawget(rawget(g, '_binds'), k) + if not t then + t = {} + rawset(rawget(g, '_binds'), k, t) + rawset(rawget(g, '_names'), t, k) + end + return t + end, + __newindex = function(g, k, v) + local t = rawget(rawget(g, '_binds'), k) + if t and type(v) == 'table' then + for k, v in pairs(v) do + t[k] = v + end + else + rawset(rawget(g, '_binds'), k, v) + if type(v) == 'table' then + rawset(rawget(g, '_names'), v, k) + end + end + end +}) + +defaults = {} +defaults.ResetKey = '`' +defaults.BackKey = 'backspace' +defaults.Verbose = false +defaults.VerboseOutput = 'Text' +defaults.Label = {} + +settings = config.load(defaults) + +label = texts.new(settings.Label, settings) + +binds = {} +names = {} +current = binds +stack = L{binds} +keys = S{} + +output = function() + if settings.Verbose then + names[current] = names[current] or 'Unnamed ' .. tostring(current):sub(8) + + if settings.VerboseOutput == 'Text' then + label:text(names[current]) + elseif settings.VerboseOutput == 'Chat' then + log('Changing into macro set %s.':format(names[current])) + elseif settings.VerboseOutput == 'Console' then + print('Changing into macro set %s.':format(names[current])) + end + end +end + +reset = function() + current = binds + stack = L{binds} + output() +end + +back = function() + if stack:length() == 1 then + current = binds + else + current = stack[stack:length() - 1] + stack:remove() + end + output() +end + +check = function(keyset) + keyset = keyset or keys + for key, val in pairs(current) do + if key <= keyset then + if type(val) == 'string' then + windower.send_command(val) + elseif type(val) == 'function' then + val() + else + current = val + stack:append(current) + output() + end + + return true + end + end + + return false +end + +parse_binds = function(fbinds, top) + top = top or binds + + rawset(names, top, rawget(_innerG._names, fbinds)) + for key, val in pairs(fbinds) do + key = S(key:split('+')):map(string.lower) + if type(val) == 'string' or type(val) == 'function' then + rawset(top, key, val) + else + local sub = {} + rawset(top, key, sub) + parse_binds(val, sub) + end + end +end + +windower.register_event('load', 'login', 'job change', 'logout', function() + local player = windower.ffxi.get_player() + local file, path, filename, filepath, err + local basepath = windower.addon_path .. 'data/' + if player then + for filepath_template in L{ + {path = 'name_main_sub.lua', format = '%s\'s %s/%s'}, + {path = 'name_main.lua', format = '%s\'s %s'}, + {path = 'name.lua', format = '%s\'s'}, + {path = 'binds.lua', format = '"binds"'}, + }:it() do + path = filepath_template.format:format(player.name, player.main_job, player.sub_job or '') + filename = filepath_template.path:gsub('name', player.name):gsub('main', player.main_job):gsub('sub', player.sub_job or '') + filepath = basepath .. filename + if windower.file_exists(filepath) then + file, err = loadfile(filepath) + break + end + end + end + + if file and not err then + _innerG._names = {} + _innerG._binds = {} + binds = {} + names = {} + keys = S{} + + setfenv(file, _innerG) + local root = file() + if not root then + _innerG._names = {} + _innerG._binds = {} + error('Malformatted %s Lua file: no return value.':format(path)) + return + end + + _innerG._names[root] = _innerG._names[root] or 'Root' + parse_binds(root) + reset() + + print('Yush: Loaded %s Lua file':format(path)) + elseif err then + print('\nYush: Error loading file: '..err:gsub('\\','/')) + elseif player then + print('Yush: No matching file found for %s (%s%s)':format(player.name, player.main_job, player.sub_job and '/' .. player.sub_job or '')) + + end +end) + +dikt = { -- Har har + [1] = 'esc', + [2] = '1', + [3] = '2', + [4] = '3', + [5] = '4', + [6] = '5', + [7] = '6', + [8] = '7', + [9] = '8', + [10] = '9', + [11] = '0', + [12] = '-', + [13] = '=', + [14] = 'backspace', + [15] = 'tab', + [16] = 'q', + [17] = 'w', + [18] = 'e', + [19] = 'r', + [20] = 't', + [21] = 'y', + [22] = 'u', + [23] = 'i', + [24] = 'o', + [25] = 'p', + [26] = '[', + [27] = ']', + [28] = 'enter', + [29] = 'ctrl', + [30] = 'a', + [31] = 's', + [32] = 'd', + [33] = 'f', + [34] = 'g', + [35] = 'h', + [36] = 'j', + [37] = 'k', + [38] = 'l', + [39] = ';', + [40] = '\'', + [41] = '`', + [42] = 'shift', + [43] = '\\', + [44] = 'z', + [45] = 'x', + [46] = 'c', + [47] = 'v', + [48] = 'b', + [49] = 'n', + [50] = 'm', + [51] = ',', + [52] = '.', + [53] = '/', + [54] = nil, + [55] = 'num*', + [56] = 'alt', + [57] = 'space', + [58] = nil, + [59] = 'f1', + [60] = 'f2', + [61] = 'f3', + [62] = 'f4', + [63] = 'f5', + [64] = 'f6', + [65] = 'f7', + [66] = 'f8', + [67] = 'f9', + [68] = 'f10', + [69] = 'num', + [70] = 'scroll', + [71] = 'num7', + [72] = 'num8', + [73] = 'num9', + [74] = 'num-', + [75] = 'num4', + [76] = 'num5', + [77] = 'num6', + [78] = 'num+', + [79] = 'num1', + [80] = 'num2', + [81] = 'num3', + [82] = 'num0', + + [199] = 'home', + [200] = 'up', + [201] = 'pageup', + [202] = nil, + [203] = 'left', + [204] = nil, + [205] = 'right', + [206] = nil, + [207] = 'end', + [208] = 'down', + [209] = 'pagedown', + [210] = 'insert', + [211] = 'delete', + [219] = 'win', + [220] = 'rwin', + [221] = 'apps', +} + +windower.register_event('keyboard', function(dik, down) + local key = dikt[dik] + if not key then + return + end + + if not down then + keys:remove(key) + return + end + + if not keys:contains(key) then + keys:add(key) + + if not windower.ffxi.get_info().chat_open then + if key == settings.ResetKey then + reset() + return true + elseif key == settings.BackKey then + back() + return true + end + end + + return check() + end +end) + +windower.register_event('prerender', function() + if settings.Verbose and settings.VerboseOutput == 'Text' then + label:show() + else + label:hide() + end +end) + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + local args = {...} + + if command == 'reset' then + reset() + + elseif command == 'back' then + back() + + elseif command == 'press' then + check(S(args):map(string.lower)) + + elseif command == 'set' then + if not args[1] then + error('Specify a settings category.') + return + end + + local category = args[1]:lower() + local param = args[2] and args[2]:lower() or nil + + if category == 'verbose' then + if param == 'true' then + settings.Verbose = true + elseif param == 'false' then + settings.Verbose = false + elseif param == 'toggle' then + settings.Verbose = not settings.Verbose + else + log('Verbose settings are %s.':format(settings.Verbose and 'on' or 'off')) + return + end + + elseif category == 'backkey' then + if not param then + log('Current "Back" key: %s':format(settings.BackKey)) + return + elseif not table.find(param) then + error('Key %s unknown.':format(param)) + return + else + settings.BackKey = param + end + + elseif category == 'resetkey' then + if not param then + log('Current "Reset" key: %s':format(settings.ResetKey)) + return + elseif not table.find(param) then + error('Key %s unknown.':format(param)) + return + else + settings.ResetKey = param + end + + elseif category == 'verboseoutput' then + if not param then + log('Currently verbose mode outputs to %s.':format( + settings.VerboseOutput == 'Text' and 'a text object' + or settings.VerboseOutput == 'Chat' and 'the chat log' + or settings.VerboseOutput == 'Console' and 'the console' + )) + return + elseif param == 'text' then + settings.VerboseOutput = 'Text' + elseif param == 'chat' then + settings.VerboseOutput = 'Chat' + elseif param == 'console' then + settings.VerboseOutput = 'Console' + end + + end + + config.save(settings) + + elseif command == 'save' then + config.save(settings, 'all') + + end +end) + +--[[ +Copyright © 2014, Windower +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 Windower 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 Windower 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. +]] |