├── .gitignore ├── AutoWS └── AutoWS.lua ├── GMmode └── GMmode.lua ├── README.txt ├── StratagemCounter └── StratagemCounter.lua ├── autoSynth └── autoSynth.lua ├── azureSets └── data │ └── settings.xml ├── battlemod └── data │ ├── colors.xml │ ├── filters │ └── filters.xml │ └── settings.xml ├── bluSets ├── Readme.md ├── bluSets.lua └── data │ └── settings.lua ├── camper └── camper.lua ├── event13helper └── event13helper.lua ├── eventScripts ├── data │ └── settings.xml └── eventScripts.lua ├── infoBoxes ├── infoBox.lua └── infoBoxes.lua ├── jponry └── jponry.lua ├── shortcuts └── data │ └── aliases.xml └── spellSpammer └── spellSpammer.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.stackdump 2 | .idea/ 3 | autolock/* 4 | autoSynth/data/* 5 | AutoRA/* 6 | AutoWS/data/* 7 | azureSets/Readme.md 8 | azureSets/azuresets.lua 9 | azureSets/data/*7z 10 | azureSets/data/*old.xml 11 | barfiller/* 12 | battlemod/battlemod.lua 13 | battlemod/data/filters/filters - Copy (2).xml 14 | battlemod/data/filters/filters - Copy (3).xml 15 | battlemod/data/filters/filters - Copy.xml 16 | battlemod/generic_helpers.lua 17 | battlemod/parse_action_packet.lua 18 | battlemod/README.md 19 | battlemod/statics.lua 20 | BLUAlert/ 21 | bluguide/* 22 | boxdestroyer/* 23 | camper/data/settings.lua 24 | cancel/cancel.lua 25 | chars/* 26 | ChatLink/* 27 | Clock/ 28 | ConsoleBG/* 29 | discarded code.lua 30 | DistancePlus/ 31 | DressUp/* 32 | enemybar/* 33 | eval/* 34 | FastCS/* 35 | findAll/* 36 | GearSwap/* 37 | GearSwap.backup/* 38 | GMmode/bintest.txt 39 | HealBot* 40 | healBot_*.7z 41 | highlight/* 42 | info/* 43 | InfoBar/ 44 | InfoReplacer/ 45 | instaLS/* 46 | JobChange/ 47 | libs/* 48 | Logger/* 49 | Lookup/ 50 | macrochanger/* 51 | MobCompass/ 52 | Nostrum/ 53 | OhNoYouDont* 54 | ohShi/* 55 | Omen/ 56 | organizer/* 57 | ParseBoard/* 58 | pet_fix/pet_fix.lua 59 | PetSchool/* 60 | PetTP/* 61 | plasmon/* 62 | pointwatch/* 63 | porter/* 64 | Pouches/* 65 | reive/* 66 | scoreboard/* 67 | ScriptedExtender 68 | send/* 69 | shortcuts/data/aliases_default.xml 70 | shortcuts/helper_functions.lua 71 | shortcuts/readme.md 72 | shortcuts/shortcuts.lua 73 | shortcuts/statics.lua 74 | shortcuts/targets.lua 75 | Silence/* 76 | sparkGil/sparkGil.lua 77 | SpellCheck/* 78 | spellSpammer/spellSpammer_b.lua 79 | stopwatch/* 80 | timestamp/* 81 | Text/* 82 | thtracker/* 83 | TParty/* 84 | translate/* 85 | Treasury/* 86 | update/* 87 | RAWR/ 88 | Rhombus/ 89 | SpeedChecker/ 90 | SubTarget/ 91 | TreasurePool/ 92 | VisibleFavor/ 93 | aatest/ 94 | autoinvite/ 95 | autojoin/ 96 | capetrader/ 97 | cellhelp/ 98 | craft/ 99 | distance/ 100 | dynamishelper/ 101 | gametime/ 102 | giltracker/ 103 | invtracker/ 104 | itemizer/ 105 | latentchecker/ 106 | linker/ 107 | lottery/ 108 | obiaway/ 109 | plugin_manager/ 110 | remember/ 111 | roe/ 112 | rolltracker/ 113 | salvage2/ 114 | setbgm/ 115 | stna/ 116 | targetinfo/ 117 | temps/ 118 | vwhl/ 119 | xivbar/ 120 | zonetimer/ -------------------------------------------------------------------------------- /AutoWS/AutoWS.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'AutoWS' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'autows','aws'} 4 | _addon.version = '0.3.1' 5 | _addon.lastUpdate = '2016.08.01' 6 | 7 | --[[ 8 | TODO: Add per-mob WS settings 9 | --]] 10 | 11 | require('luau') 12 | require('lor/lor_utils') 13 | _libs.lor.req('all') 14 | _libs.lor.debug = false 15 | 16 | local rarr = string.char(129,168) 17 | local bags = {[0]='inventory',[8]='wardrobe',[10]='wardrobe2',[11]='wardrobe3',[12]='wardrobe4'} 18 | 19 | local hps, mobs 20 | local enabled = false 21 | local useAutoRA = false 22 | local araDelayed = 0 23 | local ws_cmd = '' 24 | local autowsDelay = 0.8 25 | local defaults = {hps = {['<']=100, ['>']=5}} 26 | settings = _libs.lor.settings.load('data/settings.lua', defaults) 27 | local settings_loaded = false 28 | 29 | 30 | local function weap_type() 31 | local items = windower.ffxi.get_items() 32 | local i,bag = items.equipment.main, items.equipment.main_bag 33 | local skill = 'Hand-to-Hand' 34 | if i ~= 0 then --0 => nothing equipped 35 | skill = res.skills[res.items[items[bags[bag]][i].id].skill].en 36 | end 37 | return skill 38 | end 39 | 40 | 41 | function save_settings() 42 | local player = windower.ffxi.get_player() 43 | local name = player.name 44 | local job = player.main_job 45 | local skill = weap_type() 46 | 47 | settings[name] = settings[name] or {} 48 | settings[name][job] = settings[name][job] or {} 49 | settings[name][job][skill] = settings[name][job][skill] or {} 50 | settings[name][job][skill].hps = hps 51 | settings[name][job][skill].mobs = mobs 52 | settings[name][job][skill].ws_cmd = ws_cmd 53 | settings:save() 54 | end 55 | 56 | 57 | function load_settings() 58 | local p = windower.ffxi.get_player() 59 | if p == nil then return end 60 | local s = settings:get_nested_value(p.name, p.main_job, weap_type()) or {} 61 | hps = s.hps or defaults.hps 62 | mobs = s.mobs or {} 63 | ws_cmd = s.ws_cmd or '' 64 | settings_loaded = true 65 | end 66 | 67 | 68 | local function parse_hps(arg_str) 69 | local srx = {['<'] = '<%s*(%d+)', ['>'] = '>%s*(%d+)', ['='] = '=%s*(%d+)'} 70 | local vals = map(tonumber, map(customized(string.match, arg_str), srx)) 71 | if vals['='] ~= nil then 72 | if sizeof(vals) == 1 then 73 | vals['<'] = vals['='] + 1 74 | vals['>'] = vals['='] - 1 75 | vals['='] = nil 76 | else 77 | atc(123, 'Input Error: Only accepts HP% >/< OR =, not both!') 78 | return {} 79 | end 80 | end 81 | if sizeof(vals) < 1 then 82 | atc(123, 'Error: Invalid HP format; see //autows help') 83 | end 84 | return vals 85 | end 86 | 87 | 88 | local function valid_hp_args(args) 89 | local vals = {['<'] = args['<'] or hps['<'], ['>'] = args['>'] or hps['>']} 90 | for s,v in pairs(vals) do 91 | if not (-1 <= v and v <= 101) then 92 | atcf(123, 'Input Error: HP%% %s %s must be between 0 and 100', s, v) 93 | return false 94 | end 95 | end 96 | if vals['>'] > vals['<'] then 97 | atcf(123, 'Input Error: HP%% > %s must be < HP%% < %s', vals['>'], vals['<']) 98 | return false 99 | end 100 | return true 101 | end 102 | 103 | 104 | windower.register_event('addon command', function (command,...) 105 | command = command and command:lower() or 'help' 106 | local args = T{...} 107 | local arg_str = windower.convert_auto_trans((' '):join(args)) 108 | 109 | if S{'reload','unload'}:contains(command) then 110 | windower.send_command(('lua %s %s'):format(command, _addon.name)) 111 | elseif S{'enable','on','start'}:contains(command) then 112 | enabled = true 113 | print_status() 114 | elseif S{'disable','off','stop'}:contains(command) then 115 | enabled = false 116 | print_status() 117 | elseif command == 'toggle' then 118 | enabled = not enabled 119 | print_status() 120 | elseif S{'set','use','ws'}:contains(command) then 121 | ws_cmd = ('/ws "%s" '):format(arg_str) 122 | save_settings() 123 | print_status() 124 | elseif command == 'hp' then 125 | local parsed = parse_hps(arg_str) 126 | if sizeof(parsed) < 1 then return end 127 | if not valid_hp_args(parsed) then return end 128 | hps['<'] = parsed['<'] or hps['<'] 129 | hps['>'] = parsed['>'] or hps['>'] 130 | save_settings() 131 | print_status() 132 | elseif command == 'mob' then 133 | local mob_name = arg_str:match('[<>%d%s]*([^<>%d]+)[<>%d%s]*'):trim() 134 | if mob_name == nil or #mob_name < 1 then 135 | atc(123, 'Error: unable to parse mob name') 136 | return 137 | end 138 | if S{'t',''}:contains(mob_name) then 139 | local mob = windower.ffxi.get_mob_by_target() 140 | if mob == nil or mob.name == nil then 141 | atcf(123, 'Error: Mob name was \'%s\' but no target was found!', mob_name) 142 | return 143 | end 144 | mob_name = mob.name 145 | end 146 | local parsed = parse_hps(arg_str) 147 | if sizeof(parsed) < 1 then return end 148 | if not valid_hp_args(parsed) then 149 | atc(262, 'Note: Consider changing the defaults or providing both HP values if you left one out') 150 | return 151 | end 152 | local msg = { 153 | ['<'] = parsed['<'] or '(default)', 154 | ['>'] = parsed['>'] or '(default)' 155 | } 156 | atcf('WS %s %s @ %d < HP%% < %s', rarr, mob_name, msg['>'], msg['<']) 157 | mobs[mob_name] = {['<'] = parsed['<'], ['>'] = parsed['>']} 158 | save_settings() 159 | elseif command == 'mobs' then 160 | pprint_tiered(mobs) 161 | elseif command == 'autora' then 162 | local cmd = args[2] and args[2]:lower() or (useAutoRA and 'off' or 'on') 163 | if S{'on'}:contains(cmd) then 164 | useAutoRA = true 165 | atc('AutoWS will now resume auto ranged attacks after WSing') 166 | elseif S{'off'}:contains(cmd) then 167 | useAutoRA = false 168 | atc('AutoWS will no longer resume auto ranged attacks after WSing') 169 | else 170 | atc(123,'Error: invalid argument for AutoRA: '..cmd) 171 | end 172 | elseif command == 'status' then 173 | print_status() 174 | elseif S{'help','--help'}:contains(command) then 175 | print_help() 176 | elseif command == 'info' then 177 | if not _libs.lor.exec then 178 | atc(3,'Unable to parse info. Windower/addons/libs/lor/lor_exec.lua was unable to be loaded.') 179 | atc(3,'If you would like to use this function, please visit https://github.com/lorand-ffxi/lor_libs to download it.') 180 | return 181 | end 182 | local cmd = args[1] --Take the first element as the command 183 | table.remove(args, 1) --Remove the first from the list of args 184 | _libs.lor.exec.process_input(cmd, args) 185 | else 186 | atc('Error: Unknown command') 187 | end 188 | end) 189 | 190 | 191 | windower.register_event('load', function() 192 | if not _libs.lor then 193 | windower.add_to_chat(39,'ERROR: .../Windower/addons/libs/lor/ not found! Please download: https://github.com/lorand-ffxi/lor_libs') 194 | end 195 | atcc(262, 'Welcome to AutoWS! It is recommended to use HP < 100 to prevent immediate WS on engage when too far away.') 196 | autowsLastCheck = os.clock() 197 | load_settings() 198 | end) 199 | 200 | 201 | windower.register_event('logout', function() 202 | windower.send_command('lua unload autows') 203 | end) 204 | 205 | 206 | windower.register_event('zone change', function(new_id, old_id) 207 | autowsLastCheck = os.clock() + 15 208 | end) 209 | 210 | 211 | windower.register_event('job change', function() 212 | enabled = false 213 | end) 214 | 215 | 216 | windower.register_event('prerender', function() 217 | if not settings_loaded then 218 | if windower.ffxi.get_player() ~= nil then 219 | load_settings() 220 | end 221 | end 222 | if enabled and (ws_cmd ~= '') then 223 | local now = os.clock() 224 | if (now - autowsLastCheck) >= autowsDelay then 225 | local player = windower.ffxi.get_player() 226 | local mob = windower.ffxi.get_mob_by_target() 227 | if (player ~= nil) and (player.status == 1) and (mob ~= nil) then 228 | local hp_lt = table.get_nested_value(mobs, mob.name, '<') or hps['<'] 229 | local hp_gt = table.get_nested_value(mobs, mob.name, '>') or hps['>'] 230 | if player.vitals.tp > 999 then 231 | if useAutoRA and (araDelayed < 2) then 232 | araDelayed = araDelayed + 1 233 | else 234 | if hp_gt < mob.hpp and mob.hpp < hp_lt then 235 | windower.send_command(('input %s'):format(ws_cmd)) 236 | end 237 | araDelayed = 0 238 | if useAutoRA then 239 | windower.send_command('wait 4;ara start') 240 | end 241 | end 242 | end 243 | end 244 | autowsLastCheck = now 245 | end 246 | end 247 | end) 248 | 249 | 250 | function print_status() 251 | local power = enabled and 'ON' or 'OFF' 252 | local ws_msg = #ws_cmd > 1 and ws_cmd or '(no ws specified)' 253 | atcf('[AutoWS: %s] %s %s mobs @ %d < HP%% < %s', power, ws_msg, rarr, hps['>'], hps['<']) 254 | end 255 | 256 | 257 | function print_help() 258 | local help = T{ 259 | ['[on|off|toggle]'] = 'Enable / disable autoWS', 260 | ['mob (>|<) (hp%) name'] = 'Set a different HP value for a specific mob name', 261 | ['hp (>|<) (hp%)'] = 'Set the default HP value for when weaponskills should be executed', 262 | ['use weaponskill_name'] = 'Set the weaponskill that should be used', 263 | ['autora (on|off)'] = 'Enable / disable the AutoRA addon', 264 | } 265 | --local mwwidth = max(unpack(map(string.wlen, table.keys(help)))) 266 | local mwwidth = col_width(help:keys()) 267 | atcc(262, 'AutoWS commands:') 268 | for cmd,desc in opairs(help) do 269 | atc(cmd:rpad(' ', mwwidth):colorize(263), desc:colorize(1)) 270 | end 271 | end 272 | 273 | 274 | ----------------------------------------------------------------------------------------------------------- 275 | --[[ 276 | Copyright © 2016, Lorand 277 | All rights reserved. 278 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 279 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 280 | * 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. 281 | * Neither the name of ffxiHealer nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 282 | 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 Lorand 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. 283 | --]] 284 | ----------------------------------------------------------------------------------------------------------- 285 | -------------------------------------------------------------------------------- /GMmode/GMmode.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'GMmode' 2 | _addon.author = 'Lorand' 3 | _addon.command = 'gmm' 4 | _addon.version = '1.0' 5 | 6 | packets = require('packets') 7 | 8 | local flags = {['POL']=0x60, ['GM']=0x80, ['GM1']=0x80, ['GM2']=0xA0, ['GM3']=0xC0, ['SGM']=0xE0} 9 | local flagMap = {[0x60]='POL', [0x80]='GM1', [0xA0]='GM2', [0xC0]='GM3', [0xE0]='SGM'} 10 | local useFlag = 0xE0 11 | 12 | windower.register_event('addon command', function (command,...) 13 | command = command and command:lower() or 'help' 14 | local args = {...} 15 | 16 | if command == 'reload' then 17 | windower.send_command('lua reload GMmode') 18 | elseif command == 'unload' then 19 | windower.send_command('lua unload GMmode') 20 | elseif S{'use', 'flag', 'useflag', 'set', 'setflag'}:contains(command) then 21 | if args[1] ~= nil then 22 | local flagType = args[1]:upper() 23 | if flags[flagType] then 24 | useFlag = flags[flagType] 25 | atc('Flag set to '..flagType..'. Rest to update your character.') 26 | else 27 | atc('Error: Invalid flag name.') 28 | helpText() 29 | end 30 | else 31 | atc('Error: No flag name was provided.') 32 | helpText() 33 | end 34 | else 35 | helpText() 36 | end 37 | end) 38 | 39 | windower.register_event('incoming chunk',function (id, data) 40 | if id == 0x037 then 41 | local parsed = packets.parse('incoming', data) 42 | local plyr = windower.ffxi.get_player() 43 | if (parsed.Player == plyr.id) then 44 | parsed._flags2 = useFlag 45 | local rebuilt = packets.build(parsed) 46 | return rebuilt 47 | end 48 | end 49 | end) 50 | 51 | function atc(text) 52 | windower.add_to_chat(0, '[GMmode]'..text) 53 | end 54 | 55 | function helpText() 56 | atc('//gmm use flagName : Use flag flagName') 57 | atc('Valid flags: POL, GM, GM2, GM3, SGM') 58 | atc('Current flag: '..flagMap[useFlag]) 59 | end -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Misc. Addons for FFXI 2 | 3 | Note: HealBot, info, and OhNoYouDont have been moved to their own separate repositories. 4 | 5 | Installation Tutorial: 6 | https://www.googledrive.com/host/0B1cgwOaoHrytRFFiZllWQTRnYUU/addons.html 7 | -------------------------------------------------------------------------------- /StratagemCounter/StratagemCounter.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'StratagemCounter' 2 | _addon.author = 'Lorand' 3 | _addon.command = 'StratagemCounter' 4 | _addon.version = '0.1' 5 | 6 | require('luau') 7 | texts = require('texts') 8 | 9 | local strat_charge_time = {[1]=240,[2]=120,[3]=80,[4]=60,[5]=48} 10 | local settings = {pos={x=0,y=0}} 11 | local stratBox = texts.new(settings) 12 | 13 | windower.register_event('load', 'login', function() 14 | stratBox = texts.new(settings) 15 | end) 16 | 17 | windower.register_event('prerender', function() 18 | player = windower.ffxi.get_player() 19 | if player then 20 | if S{player.main_job, player.sub_job}:contains('SCH') then 21 | local strats = get_available_stratagem_count() 22 | local col = '\\cs(0,255,0)' 23 | if (strats == 0) then 24 | col = '\\cs(255,0,0)' 25 | end 26 | stratBox:text('Stratagems: '..col..strats..'\\cr') 27 | stratBox:visible(true) 28 | else 29 | stratBox:hide() 30 | end 31 | else 32 | stratBox:hide() 33 | end 34 | end) 35 | 36 | --[[ 37 | Calculates and returns the maximum number of SCH stratagems available for use. 38 | --]] 39 | function get_max_stratagem_count() 40 | if S{player.main_job, player.sub_job}:contains('SCH') then 41 | local lvl = (player.main_job == 'SCH') and player.main_job_level or player.sub_job_level 42 | return math.floor(((lvl - 10) / 20) + 1) 43 | else 44 | return 0 45 | end 46 | end 47 | 48 | --[[ 49 | Calculates the number of SCH stratagems that are currently available for use. Calculated from the combined recast timer for stratagems and the maximum number 50 | of stratagems that are available. The recast time for each stratagem charge corresponds directly with the maximum number of stratagems that can be used. 51 | --]] 52 | function get_available_stratagem_count() 53 | local recastTime = windower.ffxi.get_ability_recasts()[231] or 0 54 | local maxStrats = get_max_stratagem_count() 55 | if (maxStrats == 0) then return 0 end 56 | local stratsUsed = (recastTime/strat_charge_time[maxStrats]):ceil() 57 | return maxStrats - stratsUsed 58 | end 59 | 60 | --====================================================================================================================== 61 | --[[ 62 | Copyright © 2015, Lorand 63 | All rights reserved. 64 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 65 | following conditions are met: 66 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 67 | following disclaimer. 68 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 69 | following disclaimer in the documentation and/or other materials provided with the distribution. 70 | * Neither the name of StratagemCounter nor the names of its contributors may be used to endorse or promote 71 | products derived from this software without specific prior written permission. 72 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 73 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 74 | DISCLAIMED. IN NO EVENT SHALL Lorand BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 75 | CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 76 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 77 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 78 | POSSIBILITY OF SUCH DAMAGE. 79 | --]] 80 | --====================================================================================================================== -------------------------------------------------------------------------------- /autoSynth/autoSynth.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'autoSynth' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'autoSynth', 'as'} 4 | _addon.version = '1.5.1' 5 | _addon.lastUpdate = '2016.11.26' 6 | 7 | require('luau') 8 | require('lor/lor_utils') 9 | _libs.lor.include_addon_name = true 10 | _libs.req('texts') 11 | _libs.lor.req('all') 12 | 13 | local packets = require('packets') 14 | 15 | local synthesisPossible = false 16 | local baseDelay = 2.1 17 | local qualities = {[0]='NQ', [1]='Break', [2]='HQ'} 18 | local crystals = {[16]='Water',[17]='Wind',[18]='Fire',[19]='Earth',[20]='Lightning',[21]='Ice',[22]='Light',[23]='Dark'} 19 | local qual_count_keys = {[1]='breaks',[2]='hq'} 20 | local rarr = string.char(129,168) 21 | 22 | local compass = {n = -math.pi/2, s = math.pi/2, e = 0, w = math.pi, nw = -math.pi*3/4, ne = -math.pi*1/4, sw = math.pi*3/4, se = math.pi*1/4} 23 | local safe = {dark = 'n', light = 'ne', ice = 'e', wind = 'se', earth = 's', lightning = 'sw', water = 'w', fire = 'nw'} 24 | local risk = {dark = 'ne', light = 'n', ice = 'nw', wind = 'e', earth = 'se', lightning = 's', water = 'sw', fire = 'w'} 25 | 26 | local overall = {skillups = 0, skillup_count = 0, synths = 0, breaks = 0, hq = 0, hqT = {0,0,0}} 27 | local session = {skillups = 0, skillup_count = 0, synths = 0, breaks = 0, hq = 0, hqT = {0,0,0}} 28 | local req_food = false 29 | local req_supp = false 30 | local queued = -1 31 | local stop_skill 32 | local stop_level = -1 33 | 34 | local saved_info = _libs.lor.settings.load('data/saved_info.lua') 35 | local default_skill_box_settings = {skill_box={pos={x=-400,y=0}, flags={right=true,bottom=false}, text={font='Arial',size=10}}} 36 | local skill_box_settings = _libs.lor.settings.load('data/settings.lua', default_skill_box_settings).skill_box 37 | local skill_box 38 | local lc_skills 39 | 40 | _debug = false 41 | 42 | 43 | function print_stats(stats, header) 44 | atcfs('%s stats:', header) 45 | if stats.skillup_count > 0 then 46 | local pl = (stats.skillup_count > 1) and 's' or '' 47 | atcfs('Skill increase: %.1f (%d skillup%s) | Avg: %.2f per skillup, %.2f per synth | Rate: %.2f%%', stats.skillups, stats.skillup_count, pl, stats.skillups/stats.skillup_count, stats.skillups/stats.synths, stats.skillup_count/stats.synths*100) 48 | else 49 | atc('No skillups.') 50 | end 51 | 52 | if stats.synths > 0 then 53 | atcfs('Total: %d synths | HQ: %d (%.2f%%) | Break: %d (%.2f%%)', stats.synths, stats.hq, stats.hq/stats.synths*100, stats.breaks, stats.breaks/stats.synths*100) 54 | if stats.hq > 0 then 55 | local hq_msgs = {} 56 | for tier, count in ipairs(stats.hqT) do 57 | local tpct, hpct = count/stats.synths*100, count/stats.hq*100 58 | hq_msgs[#hq_msgs+1] = ('HQ%s: %d (%.2f%% overall / %.2f%% hq)'):format(tier, count, tpct, hpct) 59 | end 60 | atcfs((' | '):join(hq_msgs)) 61 | end 62 | else 63 | atc('No synths performed.') 64 | end 65 | end 66 | 67 | 68 | windower.register_event('load', function() 69 | atc('Loaded autoSynth.') 70 | atc('AutoSynth will repeat the most recent synthesis attempt via /lastsynth until you run out of materials, or it is stopped manually.') 71 | atc('Commands:') 72 | atc('//autoSynth start') 73 | atc('//autoSynth stop') 74 | 75 | local player = windower.ffxi.get_player() 76 | update_current_skill(player) 77 | refresh_skill_box(player) 78 | 79 | lc_skills = {} 80 | for _,skill in pairs(res.skills) do 81 | lc_skills[skill.en:lower()] = skill 82 | end 83 | end) 84 | 85 | 86 | windower.register_event('addon command', function (command,...) 87 | command = command and command:lower() or 'help' 88 | local args = T{...}:map(string.lower) 89 | 90 | if S{'reload','unload'}:contains(command) then 91 | windower.send_command(('lua %s %s'):format(command, _addon.name)) 92 | elseif S{'craft','start','on'}:contains(command) then 93 | start_session() 94 | elseif S{'stop','end','off'}:contains(command) then 95 | stop_session() 96 | elseif S{'require','req'}:contains(command) then 97 | for _,arg in pairs(args) do 98 | if S{'food'}:contains(arg) then 99 | req_food = true 100 | atc('Will stop crafting when food wears off.') 101 | elseif S{'support','supp','imagery'}:contains(arg) then 102 | req_supp = true 103 | atc('Will stop crafting when crafting imagery wears off.') 104 | else 105 | atcfs('Unrecognized argument for require: %s', arg) 106 | end 107 | end 108 | elseif S{'cancel','no','noreq','norequire','cancelreq'}:contains(command) then 109 | for _,arg in pairs(args) do 110 | if S{'food'}:contains(arg) then 111 | req_food = false 112 | atc('Will continue crafting if food wears off.') 113 | elseif S{'support','supp','imagery'}:contains(arg) then 114 | req_supp = false 115 | atc('Will continue crafting if crafting imagery wears off.') 116 | else 117 | atcfs('Unrecognized argument for cancel requirement: %s', arg) 118 | end 119 | end 120 | elseif S{'make','craft','do','batch'}:contains(command) then 121 | if tonumber(args[1]) then 122 | queued = tonumber(args[1]) 123 | start_session() 124 | else 125 | atcfs('Usage: %s ', command) 126 | end 127 | elseif S{'until','til','level'}:contains(command) then 128 | if (args[1] ~= nil) and tonumber(args[2]) then 129 | local arg_skill = lc_skills[args[1]:lower()] 130 | if arg_skill ~= nil then 131 | if arg_skill.category == 'Synthesis' then 132 | stop_skill = args[1] 133 | stop_level = tonumber(args[2]) 134 | start_session() 135 | else 136 | atcfs('Invalid skill: %s (only supports crafting skills)', args[1]) 137 | end 138 | else 139 | atcfs('Invalid skill: %s', args[1]) 140 | end 141 | else 142 | atcfs('Usage: %s ', command) 143 | end 144 | elseif command == 'debug' then 145 | _debug = not _debug 146 | atcfs('Debug mode: %s', _debug) 147 | elseif command == 'attempt' then 148 | trySynth() 149 | elseif S{'stat','stats','count','counts'}:contains(command) then 150 | print_stats(overall, 'Total') 151 | elseif S{'turn', 'face'}:contains(command) then 152 | face(args) 153 | end 154 | end) 155 | 156 | function printStatus() 157 | if not synthesisPossible then 158 | print_stats(session, 'Session') 159 | end 160 | atc(synthesisPossible and 'ON' or 'OFF') 161 | end 162 | 163 | function trySynth() 164 | check_buffs() 165 | if synthesisPossible then 166 | windower.send_command('input /lastsynth') 167 | queued = queued - 1 168 | end 169 | end 170 | 171 | function start_session() 172 | synthesisPossible = true 173 | session = {skillups = 0, skillup_count = 0, synths = 0, breaks = 0, hq = 0, hqT = {0,0,0}} 174 | printStatus() 175 | trySynth() 176 | end 177 | 178 | function stop_session(col, msg) 179 | if not synthesisPossible then return end 180 | synthesisPossible = false 181 | queued = -1 182 | stop_skill = nil 183 | stop_level = -1 184 | if msg ~= nil then 185 | atc(col, msg) 186 | end 187 | printStatus() 188 | end 189 | 190 | function check_buffs() 191 | local player = windower.ffxi.get_player() 192 | local active_buffs = S(player.buffs) 193 | 194 | if req_food and (not active_buffs[251]) then 195 | stop_session(123, 'Food wore off - cancelling synthesis') 196 | end 197 | 198 | if req_supp then 199 | local has_supp = false 200 | for i = 235, 243 do 201 | if active_buffs[i] then 202 | has_supp = true 203 | break 204 | end 205 | end 206 | if not has_supp then 207 | stop_session(123, 'Synthesis imagery wore off - cancelling synthesis') 208 | end 209 | end 210 | 211 | if queued == 0 then 212 | stop_session(1, 'Completed batch.') 213 | elseif stop_level > 0 then 214 | if player.skills[stop_skill] >= stop_level then 215 | stop_session(1, ('Reached skill goal [%s %s >= %s]'):format(stop_skill, player.skills[stop_skill], stop_level)) 216 | end 217 | end 218 | end 219 | 220 | function delayedAttempt() 221 | check_buffs() 222 | if synthesisPossible then 223 | local waitTime = baseDelay + math.random(0, 2) 224 | if _debug then 225 | atcfs('Initiating synthesis attempt in %ss', waitTime) 226 | end 227 | windower.send_command('wait '..waitTime..'; autoSynth attempt') 228 | end 229 | end 230 | 231 | windower.register_event('incoming text', function(original) 232 | if string.contains(original, 'Synthesis canceled.') then 233 | stop_session() 234 | elseif original == 'You must wait longer before repeating that action.' then 235 | baseDelay = baseDelay + 1 236 | if queued > -1 then 237 | queued = queued + 1 238 | end 239 | delayedAttempt() 240 | elseif original == 'You cannot use that command during synthesis.' then 241 | baseDelay = baseDelay + 2 242 | if queued > -1 then 243 | queued = queued + 1 244 | end 245 | delayedAttempt() 246 | elseif original == 'Unable to execute that command. Your inventory is full.' then 247 | stop_session() 248 | elseif original:match('%-+ %u%u Synthesis %(.+%) %-+') then --Block BattleMod synth messages 249 | return true 250 | elseif original:match('%-+ Break %(.+%) %-+') then 251 | return true 252 | elseif original:match('%-+ HQ Tier .! %-+') then 253 | return true 254 | end 255 | end) 256 | 257 | 258 | function register_skillup(packetInfo) 259 | if S{38,53}:contains(packetInfo.Message) then 260 | local skill = res.skills[packetInfo['Param 1']] 261 | if skill.category ~= 'Synthesis' then return end 262 | local player = windower.ffxi.get_player() 263 | if packetInfo.Message == 38 then 264 | update_current_skill(player, skill.en:lower(), packetInfo['Param 2']) 265 | elseif packetInfo.Message == 53 then 266 | update_current_skill(player, skill.en:lower(), nil, packetInfo['Param 2']) 267 | end 268 | refresh_skill_box(player) 269 | end 270 | end 271 | 272 | 273 | function update_current_skill(player, skill_name, incr, lvl) 274 | player = player or windower.ffxi.get_player() 275 | local do_save = false 276 | if saved_info[player.name] == nil then 277 | saved_info[player.name] = {} 278 | do_save = true 279 | end 280 | skill_name = skill_name or saved_info[player.name].last_skillup 281 | if skill_name == nil then return end 282 | if saved_info[player.name][skill_name] == nil then 283 | saved_info[player.name][skill_name] = player.skills[skill_name] 284 | do_save = true 285 | end 286 | if saved_info[player.name].last_skillup ~= skill_name then 287 | saved_info[player.name].last_skillup = skill_name 288 | do_save = true 289 | end 290 | if player.skills[skill_name] > saved_info[player.name][skill_name] then 291 | saved_info[player.name][skill_name] = player.skills[skill_name] 292 | do_save = true 293 | end 294 | if incr then 295 | saved_info[player.name][skill_name] = saved_info[player.name][skill_name] + (incr / 10) 296 | do_save = true 297 | elseif lvl then 298 | if lvl > saved_info[player.name][skill_name] then 299 | saved_info[player.name][skill_name] = lvl 300 | do_save = true 301 | end 302 | end 303 | if do_save then 304 | saved_info:save(true) 305 | end 306 | end 307 | 308 | 309 | function refresh_skill_box(player) 310 | if skill_box == nil then 311 | skill_box = _libs.texts.new(skill_box_settings) 312 | end 313 | player = player or windower.ffxi.get_player() 314 | if player ~= nil then 315 | if saved_info[player.name] ~= nil then 316 | local skill_name = saved_info[player.name].last_skillup 317 | if skill_name == nil then return end 318 | local skill_val = saved_info[player.name][skill_name] or 0 319 | skill_box:text(('%s: %.1f'):format(skill_name, skill_val)) 320 | skill_box:visible(true) 321 | end 322 | end 323 | end 324 | 325 | 326 | windower.register_event('incoming chunk', function(id,data) 327 | if id == 0x029 then 328 | local packetInfo = packets.parse('incoming', data) 329 | local pid = windower.ffxi.get_player().id 330 | if pid == packetInfo.Actor then 331 | register_skillup(packetInfo) 332 | if packetInfo.Message == 38 then 333 | local amount = packetInfo['Param 2']/10 334 | overall.skillups = overall.skillups + amount 335 | overall.skillup_count = overall.skillup_count + 1 336 | session.skillups = session.skillups + amount 337 | session.skillup_count = session.skillup_count + 1 338 | end 339 | end 340 | elseif id == 0x030 then 341 | local packetInfo = packets.parse('incoming', data) 342 | if windower.ffxi.get_player().id == packetInfo.Player then 343 | local ffInfo = windower.ffxi.get_info() 344 | local mphase = res.moon_phases[ffInfo.moon_phase].en .. ' (' .. ffInfo.moon .. '%)' 345 | local zone = res.zones[ffInfo.zone].en 346 | local day = res.days[ffInfo.day].en 347 | local weather = res.weather[ffInfo.weather].en 348 | local vTime = getVtime(ffInfo.time) 349 | 350 | local result = packetInfo.Param 351 | local element = packetInfo.Effect 352 | 353 | atcfs(8, 'Initiating %s synthesis (%s) | %s | %s | %s | %s | %s | %s', crystals[element], qualities[result], getVanadielTime(), vTime, day, weather, mphase, zone) 354 | local qck = qual_count_keys[result] 355 | if qck ~= nil then 356 | overall[qck] = overall[qck] + 1 357 | session[qck] = session[qck] + 1 358 | end 359 | overall.synths = overall.synths + 1 360 | session.synths = session.synths + 1 361 | end 362 | elseif id == 0x06F then 363 | local p = data:sub(5) 364 | if p:byte(1) == 0 then 365 | local result = p:byte(2) 366 | if result > 0 then 367 | atcfs(8, ' %s HQ Tier %s!', rarr, result) 368 | overall.hqT[result] = overall.hqT[result] + 1 369 | session.hqT[result] = session.hqT[result] + 1 370 | end 371 | end 372 | if _debug then 373 | atc('Synthesis complete') 374 | end 375 | delayedAttempt() 376 | end 377 | end) 378 | 379 | function getVtime(rawVtime) 380 | local m = rawVtime % 60 381 | local h = (rawVtime - m)/60 382 | return ('%s:%s%s'):format(h, (m < 10 and '0' or ''), m) 383 | end 384 | 385 | function getVanadielTime() 386 | local basisTime = os.time{year=2002, month=6, day=23, hour=11, min=0} --FFXI epoch 387 | local basisDate = os.date('!*t', basisTime) 388 | local basisMs = os.time(basisDate)*1000 389 | local now = os.time(os.date('!*t', os.time()))*1000 390 | 391 | local msGameDay = (24 * 60 * 60 * 1000 / 25) -- milliseconds in a game day 392 | local msRealDay = (24 * 60 * 60 * 1000) -- milliseconds in a real day 393 | 394 | local vanaDate = ((898 * 360 + 30) * msRealDay) + (now - basisMs) * 25 + 1250000 395 | 396 | local vYear = math.floor(vanaDate / (360 * msRealDay)) 397 | local vMon = math.floor((vanaDate % (360 * msRealDay)) / (30 * msRealDay)) + 1 398 | local vDate = math.floor((vanaDate % (30 * msRealDay)) / (msRealDay)) + 1 399 | local vHour = math.floor((vanaDate % (msRealDay)) / (60 * 60 * 1000)) 400 | local vMin = math.floor((vanaDate % (60 * 60 * 1000)) / (60 * 1000)) 401 | local vSec = math.floor((vanaDate % (60 * 1000)) / 1000) 402 | local vDay = math.floor((vanaDate % (8 * msRealDay)) / (msRealDay)) 403 | 404 | local vanaYear = (vYear < 1000) and '0'..vYear or vYear 405 | local vanaMon = (vMon < 10) and '0'..vMon or vMon 406 | local vanaDate = (vDate < 10) and '0'..vDate or vDate 407 | local vanaHour = (vHour < 10) and '0'..vHour or vHour 408 | local vanaMin = (vMin < 10) and '0'..vMin or vMin 409 | local vanaSec = (vSec < 10) and '0'..vSec or vSec 410 | 411 | return ('%s-%s-%s'):format(vanaYear, vanaMon, vanaDate) 412 | end 413 | 414 | function face(args) 415 | if args[1] ~= nil then 416 | if args[2] ~= nil then 417 | if S{'safe', 'nq'}:contains(args[1]) then 418 | if safe[args[2]] ~= nil then 419 | windower.ffxi.turn(compass[safe[args[2]]]) 420 | else 421 | atcfs('Error: invalid element: %s', args[2]) 422 | end 423 | elseif S{'risk', 'hq'}:contains(args[1]) then 424 | if risk[args[2]] ~= nil then 425 | windower.ffxi.turn(compass[risk[args[2]]]) 426 | else 427 | atcfs('Error: invalid element: %s', args[2]) 428 | end 429 | else 430 | atcfs('Error: invalid direction type: %s', args[1]) 431 | end 432 | elseif compass[args[1]] ~= nil then 433 | windower.ffxi.turn(compass[args[1]]) 434 | else 435 | atcfs('Error: invalid command or too few arguments.') 436 | end 437 | else 438 | atc('Error: too few arguments.') 439 | end 440 | end 441 | 442 | ----------------------------------------------------------------------------------------------------------- 443 | --[[ 444 | Copyright © 2016, Lorand 445 | All rights reserved. 446 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 447 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 448 | * 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. 449 | * Neither the name of autoSynth nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 450 | 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 Lorand 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. 451 | --]] 452 | ----------------------------------------------------------------------------------------------------------- 453 | -------------------------------------------------------------------------------- /azureSets/data/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PreserveTraits 5 | 0.65 6 | 7 | 8 | memento mori 9 | magic hammer 10 | tenebral crush 11 | glutinous dart 12 | cold wave 13 | reactor cool 14 | barrier tusk 15 | delta thrust 16 | spectral floe 17 | subduction 18 | dream flower 19 | entomb 20 | battery charge 21 | barbed crescent 22 | magic fruit 23 | diamondhide 24 | fantod 25 | occultation 26 | 27 | 28 | nat. meditation 29 | heavy strike 30 | sudden lunge 31 | thrashing assault 32 | paralyzing triad 33 | winds of promy. 34 | blazing bound 35 | delta thrust 36 | quad. continuum 37 | molting plumage 38 | erratic flutter 39 | empty thrash 40 | battery charge 41 | barbed crescent 42 | magic fruit 43 | diamondhide 44 | fantod 45 | glutinous dart 46 | 47 | 48 | nat. meditation 49 | heavy strike 50 | sudden lunge 51 | thrashing assault 52 | paralyzing triad 53 | blazing bound 54 | delta thrust 55 | quad. continuum 56 | molting plumage 57 | erratic flutter 58 | empty thrash 59 | battery charge 60 | barbed crescent 61 | magic fruit 62 | diamondhide 63 | fantod 64 | 65 | 66 | winds of promy. 67 | heavy strike 68 | erratic flutter 69 | actinic burst 70 | nat. meditation 71 | sudden lunge 72 | thrashing assault 73 | delta thrust 74 | molting plumage 75 | quad. continuum 76 | fantod 77 | empty thrash 78 | battery charge 79 | blazing bound 80 | barbed crescent 81 | magic fruit 82 | whirl of rage 83 | 84 | 85 | 86 | 87 | 88 | molting plumage 89 | empty thrash 90 | heavy strike 91 | sudden lunge 92 | erratic flutter 93 | quad. continuum 94 | barrier tusk 95 | delta thrust 96 | nat. meditation 97 | reactor cool 98 | dream flower 99 | thrashing assault 100 | battery charge 101 | barbed crescent 102 | magic fruit 103 | diamondhide 104 | fantod 105 | blazing bound 106 | occultation 107 | 108 | 109 | Firespit 110 | Heat Breath 111 | Thermal Pulse 112 | Blastbomb 113 | Infrasonics 114 | Frost Breath 115 | Ice Break 116 | Cold Wave 117 | Sandspin 118 | Magnetite Cloud 119 | Cimicine Discharge 120 | Bad Breath 121 | Acrid Stream 122 | Maelstrom 123 | Corrosive Ooze 124 | Cursed Sphere 125 | Awful Eye 126 | 127 | 128 | Hecatomb Wave 129 | Mysterious Light 130 | Leafstorm 131 | Reaving Wind 132 | Temporal Shift 133 | Mind Blast 134 | Blitzstrahl 135 | Charged Whisker 136 | Blank Gaze 137 | Radiant Breath 138 | Light of Penance 139 | Actinic Burst 140 | Death Ray 141 | Eyes On Me 142 | Sandspray 143 | 144 | 145 | molting plumage 146 | empty thrash 147 | heavy strike 148 | sudden lunge 149 | erratic flutter 150 | quad. continuum 151 | barrier tusk 152 | delta thrust 153 | nat. meditation 154 | reactor cool 155 | dream flower 156 | thrashing assault 157 | battery charge 158 | barbed crescent 159 | magic fruit 160 | diamondhide 161 | fantod 162 | blazing bound 163 | occultation 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /battlemod/data/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 207 9 | 207 10 | 207 11 | 207 12 | 207 13 | 207 14 | 207 15 | 207 16 | 207 17 | 207 18 | 207 19 | 207 20 | 475 21 | 0 22 | 501 23 | 256 24 | 38 25 | 0 26 | 0 27 | 0 28 | 0 29 | 1 30 | 0 31 | 205 32 | 207 33 | 207 34 | 207 35 | 207 36 | 207 37 | 0 38 | 208 39 | 30 40 | 259 41 | 42 | 43 | -------------------------------------------------------------------------------- /battlemod/data/filters/filters.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | false 16 | false 17 | true 18 | true 19 | true 20 | true 21 | true 22 | true 23 | false 24 | true 25 | false 26 | 27 | 28 | 29 | false 30 | false 31 | false 32 | false 33 | false 34 | false 35 | false 36 | false 37 | false 38 | true 39 | false 40 | 41 | 42 | 43 | 44 | 45 | false 46 | false 47 | true 48 | false 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | false 57 | false 58 | false 59 | false 60 | false 61 | false 62 | false 63 | false 64 | 65 | 66 | 67 | false 68 | false 69 | true 70 | false 71 | true 72 | true 73 | true 74 | false 75 | 76 | 77 | 78 | false 79 | false 80 | false 81 | false 82 | false 83 | false 84 | false 85 | false 86 | 87 | 88 | 89 | false 90 | false 91 | false 92 | false 93 | false 94 | false 95 | false 96 | false 97 | 98 | 99 | 100 | true 101 | true 102 | true 103 | true 104 | true 105 | true 106 | true 107 | true 108 | 109 | 110 | 111 | true 112 | true 113 | true 114 | true 115 | true 116 | true 117 | true 118 | true 119 | 120 | 121 | 122 | false 123 | false 124 | true 125 | false 126 | true 127 | true 128 | true 129 | false 130 | 131 | 132 | 133 | 134 | false 135 | false 136 | false 137 | false 138 | false 139 | false 140 | false 141 | false 142 | false 143 | 144 | 145 | 146 | false 147 | false 148 | false 149 | false 150 | false 151 | false 152 | false 153 | false 154 | false 155 | 156 | 157 | 158 | true 159 | true 160 | true 161 | true 162 | true 163 | true 164 | true 165 | true 166 | true 167 | 168 | 169 | 170 | true 171 | true 172 | true 173 | true 174 | true 175 | true 176 | true 177 | true 178 | true 179 | true 180 | true 181 | 182 | 183 | 184 | false 185 | false 186 | true 187 | false 188 | false 189 | true 190 | true 191 | true 192 | false 193 | true 194 | false 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /battlemod/data/settings.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorand-ffxi/addons/4da93c761eec88660cd93ce5f4ee86c8304f2759/battlemod/data/settings.xml -------------------------------------------------------------------------------- /bluSets/Readme.md: -------------------------------------------------------------------------------- 1 | **Original Author:** Ricky Gall 2 | Forked from version 1.24 3 | **Description:** 4 | Addon to make setting blue spells easier. Currently only works as blu main. 5 | 6 | **Abbreviations:** blusets, bs, blu 7 | -------------------------------------------------------------------------------- /bluSets/bluSets.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'BLUSets' 2 | _addon.version = '1.3.1' 3 | _addon.author = 'Lorand / Nitrous (Shiva)' 4 | _addon.commands = {'blusets','bs','blu'} 5 | _addon.lastUpdate = '2016.11.05.3' 6 | 7 | require('lor/lor_utils') 8 | _libs.lor.req('chat', 'tables', {n='settings',v='2016.10.23.1'}) 9 | _libs.req('tables', 'strings', 'logger', 'sets') 10 | local res = require('resources') 11 | local chat = require('chat') 12 | 13 | local spell_lists = { 14 | useless = {"Pollen","Footkick","Sprout Smack","Wild Oats","Power Attack","Metallic Body","Queasyshroom","Battle Dance","Feather Storm","Head Butt","Healing Breeze","Helldive","Blastbomb","Bludgeon","Blood Drain","Claw Cyclone","Poison Breath","Soporific","Screwdriver","Bomb Toss","Grand Slam","Wild Carrotchan","Caotic Eye","Smite of Rage","Digest","Pinecone Bomb","Jet Stream","Uppercut","Terror Touch","MP Drainkiss","Venom Shell","Stinking Gas","Mandibular Bite","Awful Eye","Blood Saber","Refueling","Self-Destruct","Feather Barrier","Flying Hip Press","Spiral Spin","Death Scissors","Seedspray","1000 Needles","Body Slam","Hydro Shot","Frypan","Spinal Cleave","Voracious Trunk","Enervation","Warm-Up","Hysteric Barrage","Cannonball","Sub-zero Smash","Ram Charge","Mind Blast","Plasma Charge","Vertical Cleave","Plenilune Embrace","Demoralizing Roar","Final Sting","Osmosis","Vapor Spray","Thunder Breath","Atra. Libation"}, 15 | need = {"Cocoon","Sickle Slash","Blank Gaze","Tail Slap","Magic Fruit","Acrid Stream","Yawn","Saline Coat","Magic Hammer","Regeneration","Fantod","Battery Charge","Empty Thrash","Magic Barrier","Delta Thrust","Whirl of Rage","Dream Flower","Heavy Strike","Occultation","Barbed Crescent","Winds of Promy.","Thrashing Assault","Barrier Tusk","Diffusion Ray","White Wind","Molting Plumage","Sudden Lunge","Nat. Meditation","Glutinous Dart","Paralyzing Triad","Retinal Glare","Carcharian Verve","Erratic Flutter","Subduction","Sinker Drill","Sweeping Gouge","Searing Tempest","Blinding Fulgor","Spectral Floe","Scouring Spate","Anvil Lightning","Silent Storm","Entomb","Tenebral Crush","Mighty Guard"}, 16 | nice = {"Animating Wail","Quad. Continuum","Blazing Bound","Mortal Ray","Sheep Song","Battle Dance","MP Drainkiss","Sound Blast","Frightful Roar","Uppercut","Memento Mori","Frenetic Rip","Infrasonics","Spinal Cleave","Zephyr Mantle","Disseverment","Diamondhide","Goblin Rush","Amplification","Vanity Dive","Temporal Shift","Evryone. Grudge","Actinic Burst","Quadrastrike","Benthic Typhoon","Thermal Pulse","Palling Salvo","Reaving Wind","Thunderbolt","Embalming Earth","Restoral","Saurian Slide","Plenilune Embrace","Amorphic Spikes","Water Bomb","Regurgitation","Charged Whisker","Rail Cannon"}, 17 | vw = {"Firespit","Heat Breath","Thermal Pulse","Blastbomb","Sandspin","Magnetite Cloud","Cimicine Discharge","Bad Breath","Acrid Stream","Maelstrom","Corrosive Ooze","Cursed Sphere","Hecatomb Wave","Mysterious Light","Leafstorm","Reaving Wind","Infrasonics","Ice Break","Cold Wave","Frost Breath","Temporal Shift","Mind Blast","Charged Whisker","Blitzstrahl","Actinic Burst","Radiant Breath","Blank Gaze","Light of Penance","Death Ray","Eyes on Me","Sandspray"} 18 | } 19 | 20 | 21 | defaults = { 22 | setmode = 'PreserveTraits', 23 | setspeed = 0.65, 24 | spellsets = { 25 | default = T{}, 26 | vw1 = T{ 27 | slot01='Firespit', slot02='Heat Breath', slot03='Thermal Pulse', slot04='Blastbomb', slot05='Infrasonics', slot06='Frost Breath', 28 | slot07='Ice Break', slot08='Cold Wave', slot09='Sandspin', slot10='Magnetite Cloud', slot11='Cimicine Discharge', 29 | slot12='Bad Breath', slot13='Acrid Stream', slot14='Maelstrom', slot15='Corrosive Ooze', slot16='Cursed Sphere', slot17='Awful Eye' 30 | }, 31 | vw2 = T{ 32 | slot01='Hecatomb Wave', slot02='Mysterious Light', slot03='Leafstorm', slot04='Reaving Wind', slot05='Temporal Shift', slot06='Mind Blast', 33 | slot07='Blitzstrahl', slot08='Charged Whisker', slot09='Blank Gaze', slot10='Radiant Breath', slot11='Light of Penance', slot12='Actinic Burst', 34 | slot13='Death Ray', slot14='Eyes On Me', slot15='Sandspray' 35 | } 36 | } 37 | } 38 | settings = _libs.lor.settings.load('data/settings.lua', defaults) 39 | 40 | local last_set_name = nil 41 | 42 | 43 | windower.register_event('addon command', function(...) 44 | if windower.ffxi.get_player()['main_job_id'] ~= 16 then 45 | error('You are not on (main) Blue Mage.') 46 | return nil 47 | end 48 | local args = T{...} 49 | if args ~= nil then 50 | local cmd = table.remove(args,1):lower() 51 | if S{'reload','unload'}:contains(cmd) then 52 | windower.send_command(('lua %s %s'):format(cmd, _addon.name)) 53 | elseif cmd == 'removeall' then 54 | remove_all_spells('trigger') 55 | elseif cmd == 'add' then 56 | if args[2] ~= nil then 57 | local slot = table.remove(args,1) 58 | local spell = args:sconcat() 59 | set_single_spell(spell:lower(),slot) 60 | end 61 | elseif cmd == 'convert' then 62 | _libs.lor.settings.convert_config('data/settings.xml', 'data/settings.lua') 63 | atc('For changes to take effect, please //blusets reload ') 64 | elseif cmd == 'save' then 65 | if args[1] ~= nil then 66 | save_set(args[1]) 67 | end 68 | elseif cmd == 'diff' then 69 | if args[1] == nil then 70 | atc('Usage: //blusets diff set_name_1 [set_name_2]') 71 | return 72 | end 73 | local name1 = args[1] 74 | local set1 = settings.spellsets[args[1]] 75 | if set1 == nil then 76 | atcfs(123, 'Invalid set name: %s', args[1]) 77 | return 78 | end 79 | local name2 = args[2] or get_current_set_name() or '[current]' 80 | local set2 = settings.spellsets[args[2]] or get_current_spellset() 81 | set1 = S(table.values(set1)) 82 | set2 = S(table.values(set2)) 83 | 84 | if set1 == set2 then 85 | atcfs('%s is identical to %s', name1, name2) 86 | else 87 | local set1_only = set1:diff(set2) 88 | if table.size(set1_only) > 0 then 89 | atcfs('Only in %s: %s', name1, (', '):join(set1_only)) 90 | end 91 | local set2_only = set2:diff(set1) 92 | if table.size(set2_only) > 0 then 93 | atcfs('Only in %s: %s', name2, (', '):join(set2_only)) 94 | end 95 | local in_both = set1:intersection(set2) 96 | if table.size(in_both) > 0 then 97 | atcfs('In both %s and %s: %s', name1, name2, (', '):join(in_both)) 98 | end 99 | end 100 | --atcfs('set1: %s', ', ':join(set1)) 101 | --atcfs('set2: %s', ', ':join(set2)) 102 | elseif S{'load', 'set'}:contains(cmd) then 103 | local set_name = get_current_set_name() 104 | if set_name == nil then 105 | last_set_name = last_set_name or 'unknown' 106 | local new_set_name = ('%s_%s'):format(last_set_name, os.date('%Y.%m.%d_%H.%M.%S')) 107 | save_set(new_set_name) 108 | end 109 | if args[1] ~= nil then 110 | set_spells(args[1], args[2] or settings.setmode) 111 | last_set_name = args[1] 112 | end 113 | elseif cmd == 'current' then 114 | local set_name = get_current_set_name() or '[unknown]' 115 | atcfs('Current spell set: %s', set_name) 116 | current_set:print() 117 | elseif cmd == 'list' then 118 | if args[1] ~= nil then 119 | if args[1] == 'sets' then 120 | get_spellset_list() 121 | else 122 | get_spellset_content(args[1]) 123 | end 124 | else 125 | atc(123, 'Error: list requires an additional argument (sets or spells)') 126 | end 127 | elseif cmd == 'check' then 128 | local which = args[1] 129 | local check_spells = spell_lists[which] 130 | if (which == nil) or (check_spells == nil) then 131 | atcfs(123, 'Please specify a valid spell list to check (%s)', ('|'):join(table.keys(spell_lists))) 132 | return 133 | end 134 | local wf_spells = windower.ffxi.get_spells() 135 | local blu_have = S{} 136 | local blu_need = S{} 137 | for _, blu_spell in pairs(check_spells) do 138 | local spell_name = blu_spell:lower() 139 | for id,spell in pairs(res.spells) do 140 | if spell.en:lower() == spell_name then 141 | if wf_spells[id] then 142 | blu_have:add(spell.en) 143 | else 144 | blu_need:add(spell.en) 145 | end 146 | end 147 | end 148 | end 149 | 150 | local prefix_map = {nice='Nice to have',need='Necessary'} 151 | local prefix = prefix_map[which] or which:ucfirst() 152 | atcfs('%s spells learned: %s', prefix, (', '):join(blu_have)) 153 | atcfs('%s spells needed: %s', prefix, (', '):join(blu_need)) 154 | elseif cmd == 'learned' then 155 | local wf_spells = windower.ffxi.get_spells() 156 | local spell_name = (' '):join(args):lower() 157 | for id,spell in pairs(res.spells) do 158 | if spell.en:lower() == spell_name then 159 | atcfs('%s: %s', spell.en, wf_spells[id]) 160 | break 161 | end 162 | end 163 | elseif cmd == 'help' then 164 | local helptext = [[BLUSets - Command List: 165 | removeall - Unsets all spells. 166 | convert -- Converts settings.xml to settings.lua 167 | load [ClearFirst|PreserveTraits] -- Set (setname)'s spells, 168 | optional parameter: ClearFirst or PreserveTraits: overrides 169 | setting to clear spells first or remove individually, 170 | preserving traits where possible. Default: use settings or 171 | preservetraits if settings not configured. 172 | add -- Set (spell) to slot (slot (number)). 173 | save -- Saves current spellset as (setname). 174 | current -- Lists currently set spells. 175 | list {sets,} -- Lists available spell sets, or spells in the given set 176 | diff [] -- Compares setname1 to setname2 if provided, or your currently equipped spells 177 | check -- Checks your learned spells against lists published in guides here: 178 | http://www.ffxiah.com/forum/topic/30626/the-beast-within-a-guide-to-blue-mage 179 | https://www.bg-wiki.com/bg/Out_of_the_BLU#Spells_You_Should_Learn 180 | learned -- Tells you whether or not you've learned the given spell 181 | help -- Shows this menu.]] 182 | for _, line in ipairs(helptext:split('\n')) do 183 | atcfs(207, '%s%s', line, chat.controls.reset) 184 | end 185 | end 186 | end 187 | end) 188 | 189 | 190 | function get_current_set_name() 191 | local current_set = get_current_spellset() 192 | local current_spells = S(table.values(current_set)) 193 | for set_name, spell_set in pairs(settings.spellsets) do 194 | if table.equals(S(table.values(spell_set)), current_spells) then 195 | return set_name 196 | end 197 | end 198 | return nil 199 | end 200 | 201 | 202 | function initialize() 203 | spells = res.spells:type('BlueMagic') 204 | get_current_spellset() 205 | end 206 | 207 | windower.register_event('load', initialize:cond(function() return windower.ffxi.get_info().logged_in end)) 208 | 209 | windower.register_event('login', initialize) 210 | 211 | windower.register_event('job change', initialize:cond(function(job) return job == 16 end)) 212 | 213 | function set_spells(spellset, setmode) 214 | if windower.ffxi.get_player()['main_job_id'] ~= 16 --[[and windower.ffxi.get_player()['sub_job_id'] ~= 16]] then 215 | error('Main job not set to Blue Mage.') 216 | return 217 | end 218 | if settings.spellsets[spellset] == nil then 219 | error('Set not defined: '..spellset) 220 | return 221 | end 222 | if is_spellset_equipped(settings.spellsets[spellset]) then 223 | log(spellset..' was already equipped.') 224 | return 225 | end 226 | 227 | log('Starting to set '..spellset..'.') 228 | if setmode:lower() == 'clearfirst' then 229 | remove_all_spells() 230 | set_spells_from_spellset:schedule(settings.setspeed, spellset, 'add') 231 | elseif setmode:lower() == 'preservetraits' then 232 | set_spells_from_spellset(spellset, 'remove') 233 | else 234 | error('Unexpected setmode: '..setmode) 235 | end 236 | end 237 | 238 | function is_spellset_equipped(spellset) 239 | return S(spellset):map(string.lower) == S(get_current_spellset()) 240 | end 241 | 242 | function set_spells_from_spellset(spellset, setPhase) 243 | local setToSet = settings.spellsets[spellset] 244 | local currentSet = get_current_spellset() 245 | 246 | if setPhase == 'remove' then 247 | -- Remove Phase 248 | for k,v in pairs(currentSet) do 249 | if not setToSet:contains(v:lower()) then 250 | setSlot = k 251 | local slotToRemove = tonumber(k:sub(5, k:len())) 252 | 253 | windower.ffxi.remove_blue_magic_spell(slotToRemove) 254 | --log('Removed spell: '..v..' at #'..slotToRemove) 255 | set_spells_from_spellset:schedule(settings.setspeed, spellset, 'remove') 256 | return 257 | end 258 | end 259 | end 260 | -- Did not find spell to remove. Start set phase 261 | -- Find empty slot: 262 | local slotToSetTo 263 | for i = 1, 20 do 264 | local slotName = ('slot%02u'):format(i) 265 | if currentSet[slotName] == nil then 266 | slotToSetTo = i 267 | break 268 | end 269 | end 270 | 271 | if slotToSetTo ~= nil then 272 | -- We found an empty slot. Find a spell to set. 273 | for k,v in pairs(setToSet) do 274 | if not currentSet:contains(v:lower()) then 275 | if v ~= nil then 276 | local spellID = find_spell_id_by_name(v) 277 | if spellID ~= nil then 278 | windower.ffxi.set_blue_magic_spell(spellID, tonumber(slotToSetTo)) 279 | --log('Set spell: '..v..' ('..spellID..') at: '..slotToSetTo) 280 | set_spells_from_spellset:schedule(settings.setspeed, spellset, 'add') 281 | return 282 | end 283 | end 284 | end 285 | end 286 | end 287 | 288 | -- Unable to find any spells to set. Must be complete. 289 | log(spellset..' has been equipped.') 290 | windower.send_command('@timers c "Blue Magic Cooldown" 60 up') 291 | end 292 | 293 | function find_spell_id_by_name(spellname) 294 | for spell in spells:it() do 295 | if spell['english']:lower() == spellname:lower() then 296 | return spell['id'] 297 | end 298 | end 299 | return nil 300 | end 301 | 302 | function set_single_spell(setspell,slot) 303 | if windower.ffxi.get_player()['main_job_id'] ~= 16 --[[and windower.ffxi.get_player()['sub_job_id'] ~= 16]] then return nil end 304 | 305 | local tmpTable = T(get_current_spellset()) 306 | for key,val in pairs(tmpTable) do 307 | if tmpTable[key]:lower() == setspell then 308 | error('That spell is already set.') 309 | return 310 | end 311 | end 312 | if tonumber(slot) < 10 then slot = '0'..slot end 313 | --insert spell add code here 314 | for spell in spells:it() do 315 | if spell['english']:lower() == setspell then 316 | --This is where single spell setting code goes. 317 | --Need to set by spell id rather than name. 318 | windower.ffxi.set_blue_magic_spell(spell['id'], tonumber(slot)) 319 | windower.send_command('@timers c "Blue Magic Cooldown" 60 up') 320 | tmpTable['slot'..slot] = setspell 321 | end 322 | end 323 | tmpTable = nil 324 | end 325 | 326 | function get_current_spellset() 327 | if windower.ffxi.get_player().main_job_id ~= 16 then return nil end 328 | return T(windower.ffxi.get_mjob_data().spells) 329 | -- Returns all values but 512 330 | :filter(function(id) return id ~= 512 end) 331 | -- Transforms them from IDs to lowercase English names 332 | :map(function(id) return spells[id].english:lower() end) 333 | -- Transform the keys from numeric x or xx to string 'slot0x' or 'slotxx' 334 | :key_map(function(slot) return ('slot%02u'):format(slot) end) 335 | end 336 | 337 | function remove_all_spells(trigger) 338 | windower.ffxi.reset_blue_magic_spells() 339 | notice('All spells removed.') 340 | end 341 | 342 | function save_set(setname) 343 | if setname == 'default' then 344 | error('Please choose a name other than default.') 345 | return 346 | end 347 | local curSpells = T(get_current_spellset()) 348 | settings.spellsets[setname] = curSpells 349 | settings:save() 350 | atcfs('Set %s saved.', setname) 351 | end 352 | 353 | function get_spellset_list() 354 | log("Listing sets:") 355 | for key,_ in pairs(settings.spellsets) do 356 | if key ~= 'default' then 357 | local it = 0 358 | for i = 1, #settings.spellsets[key] do 359 | it = it + 1 360 | end 361 | log("\t"..key..' '..settings.spellsets[key]:length()..' spells.') 362 | end 363 | end 364 | end 365 | 366 | function get_spellset_content(spellset) 367 | log('Getting '..spellset..'\'s spell list:') 368 | settings.spellsets[spellset]:print() 369 | end 370 | 371 | 372 | --[[ 373 | Copyright (c) 2016 Ragnarok.Lorand 374 | Original AzureSets Copyright (c) 2013, Ricky Gall 375 | All rights reserved. 376 | 377 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 378 | 379 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 380 | * 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. 381 | * Neither the name of azureSets nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 382 | 383 | 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 The Addon's Contributors 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. 384 | ]] 385 | -------------------------------------------------------------------------------- /bluSets/data/settings.lua: -------------------------------------------------------------------------------- 1 | return { 2 | ['setmode'] = 'PreserveTraits', 3 | ['setspeed'] = 0.65, 4 | ['spellsets'] = T{ 5 | ['aoe'] = T{ 6 | ['slot01'] = 'memento mori', 7 | ['slot02'] = 'magic hammer', 8 | ['slot03'] = 'tenebral crush', 9 | ['slot04'] = 'glutinous dart', 10 | ['slot05'] = 'cold wave', 11 | ['slot06'] = 'reactor cool', 12 | ['slot07'] = 'barrier tusk', 13 | ['slot08'] = 'delta thrust', 14 | ['slot09'] = 'spectral floe', 15 | ['slot10'] = 'subduction', 16 | ['slot11'] = 'dream flower', 17 | ['slot12'] = 'entomb', 18 | ['slot13'] = 'battery charge', 19 | ['slot14'] = 'barbed crescent', 20 | ['slot15'] = 'magic fruit', 21 | ['slot16'] = 'diamondhide', 22 | ['slot17'] = 'fantod', 23 | ['slot19'] = 'occultation' 24 | }, 25 | ['apexdd'] = T{ 26 | ['slot01'] = 'fantod', 27 | ['slot02'] = 'nat. meditation', 28 | ['slot03'] = 'delta thrust', 29 | ['slot04'] = 'barbed crescent', 30 | ['slot05'] = 'sinker drill', 31 | ['slot06'] = 'sudden lunge', 32 | ['slot07'] = 'battery charge', 33 | ['slot08'] = 'winds of promy.', 34 | ['slot09'] = 'glutinous dart', 35 | ['slot10'] = 'empty thrash', 36 | ['slot11'] = 'occultation', 37 | ['slot12'] = 'molting plumage', 38 | ['slot13'] = 'reactor cool', 39 | ['slot14'] = 'quad. continuum', 40 | ['slot15'] = 'blazing bound', 41 | ['slot16'] = 'heavy strike', 42 | ['slot17'] = 'erratic flutter', 43 | ['slot18'] = 'thrashing assault', 44 | ['slot19'] = 'blank gaze' 45 | }, 46 | ['apexdd2'] = T{ 47 | ['slot01'] = 'empty thrash', 48 | ['slot02'] = 'molting plumage', 49 | ['slot03'] = 'nat. meditation', 50 | ['slot04'] = 'delta thrust', 51 | ['slot05'] = 'occultation', 52 | ['slot06'] = 'glutinous dart', 53 | ['slot07'] = 'quad. continuum', 54 | ['slot08'] = 'erratic flutter', 55 | ['slot09'] = 'barrier tusk', 56 | ['slot10'] = 'sudden lunge', 57 | ['slot11'] = 'blazing bound', 58 | ['slot12'] = 'fantod', 59 | ['slot13'] = 'thrashing assault', 60 | ['slot14'] = 'sinker drill', 61 | ['slot15'] = 'barbed crescent', 62 | ['slot16'] = 'battery charge', 63 | ['slot17'] = 'reactor cool', 64 | ['slot18'] = 'winds of promy.', 65 | ['slot19'] = 'heavy strike', 66 | ['slot20'] = 'diamondhide' 67 | }, 68 | ['apexdd2_2016.11.19_18.07.47'] = T{ 69 | ['slot01'] = 'sudden lunge', 70 | ['slot02'] = 'molting plumage', 71 | ['slot03'] = 'nat. meditation', 72 | ['slot04'] = 'delta thrust', 73 | ['slot05'] = 'blank gaze', 74 | ['slot06'] = 'glutinous dart', 75 | ['slot07'] = 'blazing bound', 76 | ['slot08'] = 'sinker drill', 77 | ['slot09'] = 'barrier tusk', 78 | ['slot10'] = 'heavy strike', 79 | ['slot11'] = 'empty thrash', 80 | ['slot12'] = 'fantod', 81 | ['slot13'] = 'white wind', 82 | ['slot14'] = 'quad. continuum', 83 | ['slot15'] = 'barbed crescent', 84 | ['slot16'] = 'battery charge', 85 | ['slot17'] = 'reactor cool', 86 | ['slot18'] = 'occultation', 87 | ['slot19'] = 'thrashing assault', 88 | ['slot20'] = 'erratic flutter' 89 | }, 90 | ['dd_defensive'] = T{ 91 | ['slot01'] = 'molting plumage', 92 | ['slot02'] = 'delta thrust', 93 | ['slot03'] = 'barbed crescent', 94 | ['slot04'] = 'blazing bound', 95 | ['slot05'] = 'quad. continuum', 96 | ['slot06'] = 'empty thrash', 97 | ['slot07'] = 'heavy strike', 98 | ['slot08'] = 'thrashing assault', 99 | ['slot09'] = 'sudden lunge', 100 | ['slot10'] = 'fantod', 101 | ['slot11'] = 'sinker drill', 102 | ['slot12'] = 'nat. meditation', 103 | ['slot13'] = 'erratic flutter', 104 | ['slot14'] = 'battery charge', 105 | ['slot15'] = 'occultation', 106 | ['slot16'] = 'diamondhide', 107 | ['slot17'] = 'barrier tusk', 108 | ['slot18'] = 'reactor cool', 109 | ['slot19'] = 'magic fruit', 110 | ['slot20'] = 'glutinous dart' 111 | }, 112 | ['dd_defensive2'] = T{ 113 | ['slot01'] = 'molting plumage', 114 | ['slot02'] = 'delta thrust', 115 | ['slot03'] = 'barbed crescent', 116 | ['slot04'] = 'blazing bound', 117 | ['slot05'] = 'quad. continuum', 118 | ['slot06'] = 'empty thrash', 119 | ['slot07'] = 'heavy strike', 120 | ['slot08'] = 'thrashing assault', 121 | ['slot09'] = 'sudden lunge', 122 | ['slot10'] = 'fantod', 123 | ['slot11'] = 'benthic typhoon', 124 | ['slot12'] = 'nat. meditation', 125 | ['slot13'] = 'erratic flutter', 126 | ['slot14'] = 'battery charge', 127 | ['slot15'] = 'barrier tusk', 128 | ['slot16'] = 'magic fruit', 129 | ['slot17'] = 'occultation', 130 | ['slot18'] = 'reactor cool', 131 | ['slot19'] = 'diamondhide', 132 | ['slot20'] = 'whirl of rage' 133 | }, 134 | ['default'] = T{}, 135 | ['phys_stat_base_100+'] = T{ 136 | ['slot01'] = 'molting plumage', 137 | ['slot02'] = 'delta thrust', 138 | ['slot03'] = 'barbed crescent', 139 | ['slot04'] = 'blazing bound', 140 | ['slot05'] = 'quad. continuum', 141 | ['slot06'] = 'empty thrash', 142 | ['slot07'] = 'heavy strike', 143 | ['slot08'] = 'thrashing assault', 144 | ['slot09'] = 'sudden lunge', 145 | ['slot10'] = 'fantod', 146 | ['slot11'] = 'sinker drill', 147 | ['slot12'] = 'nat. meditation', 148 | ['slot13'] = 'erratic flutter' 149 | }, 150 | ['stats_only'] = T{ 151 | ['slot01'] = 'molting plumage', 152 | ['slot02'] = 'delta thrust', 153 | ['slot03'] = 'barbed crescent', 154 | ['slot04'] = 'blazing bound', 155 | ['slot05'] = 'quad. continuum', 156 | ['slot06'] = 'empty thrash', 157 | ['slot07'] = 'heavy strike', 158 | ['slot08'] = 'thrashing assault', 159 | ['slot09'] = 'sudden lunge', 160 | ['slot10'] = 'fantod', 161 | ['slot11'] = 'sinker drill', 162 | ['slot12'] = 'nat. meditation', 163 | ['slot13'] = 'erratic flutter', 164 | ['slot14'] = 'diffusion ray', 165 | ['slot15'] = 'paralyzing triad', 166 | ['slot16'] = 'benthic typhoon', 167 | ['slot17'] = 'goblin rush', 168 | ['slot18'] = 'cocoon', 169 | ['slot20'] = 'glutinous dart' 170 | }, 171 | ['vw1'] = T{ 172 | ['slot01'] = 'Firespit', 173 | ['slot02'] = 'Heat Breath', 174 | ['slot03'] = 'Thermal Pulse', 175 | ['slot04'] = 'Blastbomb', 176 | ['slot05'] = 'Infrasonics', 177 | ['slot06'] = 'Frost Breath', 178 | ['slot07'] = 'Ice Break', 179 | ['slot08'] = 'Cold Wave', 180 | ['slot09'] = 'Sandspin', 181 | ['slot10'] = 'Magnetite Cloud', 182 | ['slot11'] = 'Cimicine Discharge', 183 | ['slot12'] = 'Bad Breath', 184 | ['slot13'] = 'Acrid Stream', 185 | ['slot14'] = 'Maelstrom', 186 | ['slot15'] = 'Corrosive Ooze', 187 | ['slot16'] = 'Cursed Sphere', 188 | ['slot17'] = 'Awful Eye' 189 | }, 190 | ['vw2'] = T{ 191 | ['slot01'] = 'Hecatomb Wave', 192 | ['slot02'] = 'Mysterious Light', 193 | ['slot03'] = 'Leafstorm', 194 | ['slot04'] = 'Reaving Wind', 195 | ['slot05'] = 'Temporal Shift', 196 | ['slot06'] = 'Mind Blast', 197 | ['slot07'] = 'Blitzstrahl', 198 | ['slot08'] = 'Charged Whisker', 199 | ['slot09'] = 'Blank Gaze', 200 | ['slot10'] = 'Radiant Breath', 201 | ['slot11'] = 'Light of Penance', 202 | ['slot12'] = 'Actinic Burst', 203 | ['slot13'] = 'Death Ray', 204 | ['slot14'] = 'Eyes On Me', 205 | ['slot15'] = 'Sandspray' 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /camper/camper.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'camper' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'camper', 'camp'} 4 | _addon.version = '1.1.0' 5 | _addon.lastUpdate = '2016.10.02.01' 6 | 7 | require('lor/lor_utils') 8 | _libs.lor.include_addon_name = true 9 | _libs.lor.req('all') 10 | 11 | require('sets') 12 | require('actions') 13 | local res = require('resources') 14 | local texts = require('texts') 15 | 16 | local defaults = {box_pos={x=800,y=0}, zones={}} 17 | settings = _libs.lor.settings.load('data/settings.lua', defaults) 18 | 19 | local boxes = T{} 20 | local player 21 | local zone 22 | local track = T{} 23 | local find_mobs = S{} 24 | local last_find_scan = os.time() 25 | local find_scan_delay = 1 26 | 27 | 28 | local function refresh_vars() 29 | player = windower.ffxi.get_player() 30 | local zone_id = windower.ffxi.get_info().zone 31 | zone = res.zones[zone_id].en 32 | if settings.zones[zone] ~= nil then 33 | for mob_id,_ in pairs(settings.zones[zone]) do 34 | track_mob(mob_id, true) 35 | end 36 | end 37 | end 38 | 39 | 40 | windower.register_event('load', 'login', function() 41 | refresh_vars() 42 | end) 43 | 44 | windower.register_event('logout', function() 45 | player = nil 46 | end) 47 | 48 | windower.register_event('zone change', function(new_zone, old_zone) 49 | refresh_vars() 50 | end) 51 | 52 | windower.register_event('addon command', function(command,...) 53 | command = command and command:lower() or 'help' 54 | local args = {...} 55 | 56 | if command == 'reload' then 57 | windower.send_command(('lua unload %s; lua load %s'):format(_addon.name, _addon.name)) 58 | elseif command == 'unload' then 59 | windower.send_command(('lua unload %s'):format(_addon.name)) 60 | elseif command == 'find' then 61 | find_mob((" "):join(args), true) 62 | elseif command == 'stop_find' then 63 | find_mob((" "):join(args), false) 64 | elseif command == 'track' then 65 | track_mob(args[1]) 66 | elseif command == 'untrack' then 67 | if args[1] == nil then 68 | atc(123, 'Missing argument for untrack: index number') 69 | elseif not isnum(args[1]) then 70 | untrack_mob(tonumber(args[1])) 71 | else 72 | atc(123, 'Invalid arg for untrack') 73 | end 74 | elseif command == 'list' then 75 | pprint(pycomp('mob.id, mob.name for _,mob in windower.ffxi.get_mob_array()')) 76 | else 77 | atc(0, 'Error: Unable to parse valid command') 78 | end 79 | end) 80 | 81 | 82 | function find_mob(mob_name, add) 83 | local do_add = true 84 | if add ~= nil then do_add = add end 85 | local mname = mob_name:lower() 86 | if find_mobs:contains(mname) then 87 | if do_add then 88 | atc(123,'Already looking for that mob!') 89 | else 90 | find_mobs:remove(mname) 91 | atcfs('Will stop searching for %s', mname) 92 | end 93 | else 94 | if do_add then 95 | find_mobs:add(mname) 96 | atcfs('Will now search for %s', mname) 97 | else 98 | atc(123,'That mob was not being searched for anyways!') 99 | end 100 | end 101 | end 102 | 103 | 104 | function untrack_mob(idx) 105 | if track[idx] ~= nil then 106 | track:remove(idx) 107 | boxes[idx]:hide() 108 | boxes:remove(idx) 109 | while idx <= #boxes do 110 | local x, y = boxes[idx]:pos() 111 | if y > 0 then 112 | boxes[idx]:pos(x, y - 16) 113 | end 114 | idx = idx + 1 115 | end 116 | end 117 | end 118 | 119 | 120 | local function get_track_ids() 121 | local ids = S{} 122 | for _,cfg in pairs(track) do 123 | ids:add(cfg.id) 124 | end 125 | return ids 126 | end 127 | 128 | 129 | function track_mob(id, auto_loading) 130 | local mob_id 131 | if id ~= nil then 132 | mob_id = tonumber(id) 133 | else 134 | local mob = windower.ffxi.get_mob_by_target() 135 | if mob ~= nil then 136 | mob_id = mob.id 137 | end 138 | end 139 | 140 | if mob_id ~= nil then 141 | for _,cfg in pairs(track) do 142 | if cfg.id == mob_id then 143 | if not auto_loading then 144 | atc(123,'Already tracking that mob!') 145 | end 146 | return 147 | end 148 | end 149 | if not auto_loading then 150 | settings.zones[zone] = settings.zones[zone] or {} 151 | settings.zones[zone][mob_id] = settings.zones[zone][mob_id] or {} 152 | settings:save(true) 153 | end 154 | 155 | local to_be_tracked = { 156 | id = mob_id, 157 | ['zone'] = zone, 158 | tod = settings.zones[zone][mob_id].tod, 159 | name = settings.zones[zone][mob_id].name 160 | } 161 | track:append(to_be_tracked) 162 | local y_pos = settings.box_pos.y - 16 163 | if #boxes > 0 then 164 | _,y_pos = boxes[#boxes]:pos() 165 | end 166 | boxes:append(texts.new({pos={x=settings.box_pos.x, y=y_pos + 16}})) 167 | atcfs('Now tracking %s', mob_id) 168 | end 169 | end 170 | 171 | 172 | windower.register_event('prerender', function() 173 | if player then 174 | local now = os.time() 175 | 176 | if (#table.keys(find_mobs) > 0) and ((now - last_find_scan) > find_scan_delay) then 177 | last_find_scan = now 178 | for _,mob in pairs(windower.ffxi.get_mob_array()) do 179 | if find_mobs:contains(mob.name:lower()) and (not get_track_ids():contains(mob.id)) then 180 | track_mob(mob.id) 181 | end 182 | end 183 | end 184 | 185 | for tidx, _track in pairs(track) do 186 | if _track.id ~= nil then 187 | local mob_up = false 188 | local mob_dist = '' 189 | if _track.zone == zone then 190 | local tracked = windower.ffxi.get_mob_by_id(_track.id) 191 | if tracked ~= nil then 192 | if (settings.zones[zone][_track.id].name == nil) or (settings.zones[zone][_track.id].name == '') then 193 | settings.zones[zone][_track.id].name = tracked.name 194 | settings:save(true) 195 | end 196 | if _track.name == nil then 197 | _track.name = tracked.name 198 | end 199 | if tracked.hpp > 0 then 200 | mob_up = true 201 | _track.tod = nil 202 | if settings.zones[zone][_track.id].tod ~= nil then 203 | settings.zones[zone][_track.id].tod = nil 204 | settings:save(true) 205 | end 206 | elseif _track.tod == nil then 207 | _track.tod = now 208 | settings.zones[zone][_track.id].tod = now 209 | settings:save(true) 210 | end 211 | mob_dist = (' (%.1f)'):format(tracked.distance:sqrt()) 212 | end 213 | end 214 | 215 | local mob_name = _track.name or '[Unknown]' 216 | if mob_up then 217 | boxes[tidx]:text(('%s is UP! %s'):format(mob_name, mob_dist)) 218 | else 219 | local dtime = '--:--:--' 220 | if _track.tod ~= nil then 221 | dtime = os.date('!%H:%M:%S', now - _track.tod) 222 | end 223 | boxes[tidx]:text(('%s %s%s'):format(dtime, mob_name, mob_dist)) 224 | end 225 | boxes[tidx]:show() 226 | end 227 | end 228 | else 229 | for _,box in pairs(boxes) do 230 | box:hide() 231 | end 232 | end 233 | end) 234 | 235 | ----------------------------------------------------------------------------------------------------------- 236 | --[[ 237 | Copyright © 2016, Lorand 238 | All rights reserved. 239 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 240 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 241 | * 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. 242 | * Neither the name of info nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 243 | 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 Lorand 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. 244 | --]] 245 | ----------------------------------------------------------------------------------------------------------- 246 | -------------------------------------------------------------------------------- /event13helper/event13helper.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'event13helper' 2 | _addon.author = 'Lorand' 3 | _addon.command = 'e13h' 4 | _addon.version = '0.2' 5 | 6 | require('luau') 7 | 8 | local last = os.clock() 9 | local last_scs = S{} 10 | local last_weap_type = 'unknown' 11 | 12 | local props = { 13 | gaxe = { 14 | ['Shield Break'] = S{'Impaction'}, ['Iron Tempest'] = S{'Scission'}, ['Sturmwind'] = S{'Reverberation', 'Scission'}, 15 | ['Armor Break'] = S{'Impaction'}, ['Keen Edge'] = S{'Compression'}, ['Weapon Break'] = S{'Impaction'}, 16 | ['Raging Rush'] = S{'Induration', 'Reverberation'}, ['Full Break'] = S{'Distortion'}, ['Steel Cyclone'] = S{'Distortion', 'Detonation'}, 17 | ['Metatron Torment'] = S{'Light', 'Fusion'}, ["King's Justice"] = S{'Fragmentation', 'Scission'}, ['Fell Cleave'] = S{'Scission', 'Detonation'}, 18 | ["Ukko's Fury"] = S{'Light', 'Fragmentation'}, ['Upheaval'] = S{'Fusion', 'Compression'} 19 | }, 20 | pole = { 21 | ['Double Thrust'] = S{'Transfixion'}, ['Thunder Thrust'] = S{'Transfixion', 'Impaction'}, ['Raiden Thrust'] = S{'Transfixion', 'Impaction'}, 22 | ['Leg Sweep'] = S{'Impaction'}, ['Penta Thrust'] = S{'Compression'}, ['Vorpal Thrust'] = S{'Reverberation', 'Transfixion'}, 23 | ['Skewer'] = S{'Transfixion', 'Impaction'}, ['Wheeling Thrust'] = S{'Fusion'}, ['Impulse Drive'] = S{'Gravitation', 'Induration'}, 24 | ['Geirskogul'] = S{'Light', 'Distortion'}, ['Drakesbane'] = S{'Fusion', 'Transfixion'}, ['Sonic Thrust'] = S{'Transfixion', 'Scission'}, 25 | ["Camlann's Torment"] = S{'Light', 'Fragmentation'}, ['Stardiver'] = S{'Gravitation', 'Transfixion'} 26 | }, 27 | club = { 28 | ['Shining Strike'] = S{'Impaction'}, ['Seraph Strike'] = S{'Impaction'}, ['Brainshaker'] = S{'Reverberation'}, 29 | ['Skullbreaker'] = S{'Induration', 'Reverberation'}, ['True Strike'] = S{'Detonation', 'Impaction'}, ['Judgment'] = S{'Impaction'}, 30 | ['Hexa Strike'] = S{'Fusion'}, ['Black Halo'] = S{'Fragmentation', 'Compression'}, ['Randgrith'] = S{'Light', 'Fragmentation'}, 31 | ['Flash Nova'] = S{'Induration', 'Reverberation'}, ['Realmrazer'] = S{'Fusion', 'Impaction'} 32 | }, 33 | sword = { 34 | ['Fast Blade'] = S{'Scission'}, ['Burning Blade'] = S{'Liquefaction'}, ['Red Lotus Blade'] = S{'Liquefaction', 'Detonation'}, 35 | ['Flat Blade'] = S{'Impaction'}, ['Shining Blade'] = S{'Scission'}, ['Seraph Blade'] = S{'Scission'}, 36 | ['Circle Blade'] = S{'Reverberation', 'Impaction'}, ['Vorpal Blade'] = S{'Scission', 'Impaction'}, ['Swift Blade'] = S{'Gravitation'}, 37 | ['Savage Blade'] = S{'Fragmentation', 'Scission'}, ['Knights of Round'] = S{'Light', 'Fusion'}, ['Death Blossom'] = S{'Fragmentation', 'Distortion'}, 38 | ['Atonement'] = S{'Fusion', 'Reverberation'}, ['Expiacion'] = S{'Distortion', 'Scission'}, ['Chant du Cygne'] = S{'Light', 'Distortion'}, 39 | ['Requiescat'] = S{'Gravitation', 'Scission'} 40 | }, 41 | dagger = { 42 | ['Wasp Sting'] = S{'Scission'}, ['Viper Bite'] = S{'Scission'}, ['Shadowstitch'] = S{'Reverberation'}, 43 | ['Gust Slash'] = S{'Detonation'}, ['Cyclone'] = S{'Detonation', 'Impaction'}, ['Dancing Edge'] = S{'Scission', 'Detonation'}, 44 | ['Shark Bite'] = S{'Fragmentation'}, ['Evisceration'] = S{'Gravitation', 'Transfixion'}, ['Mercy Stroke'] = S{'Darkness', 'Gravitation'}, 45 | ['Mandalic Stab'] = S{'Fusion', 'Compression'}, ['Mordant Rime'] = S{'Fragmentation', 'Distortion'}, ['Pyrrhic Kleos'] = S{'Distortion', 'Scission'}, 46 | ['Aeolian Edge'] = S{'Impaction', 'Scission', 'Detonation'},["Rudra's Storm"] = S{'Darkness', 'Distortion'}, ['Exenterator'] = S{'Fragmentation', 'Scission'}, 47 | }, 48 | gob = { 49 | ['Bomb Toss'] = S{'Liquefaction'}, 50 | ['Goblin Rush'] = S{'Fusion', 'Impaction'}, 51 | } 52 | } 53 | 54 | local function next_weap(last_weap) 55 | if last_weap == 'gaxe' then return 'pole' 56 | elseif last_weap == 'pole' then return 'club' 57 | elseif last_weap == 'club' then return 'sword' 58 | elseif last_weap == 'sword' then return 'dagger' 59 | elseif last_weap == 'dagger' then return 'gob' 60 | elseif last_weap == 'gob' then return 'gaxe' 61 | end 62 | end 63 | 64 | local need_next = { 65 | Transfixion = {'Compression','Scission','Reverberation'}, Compression = {'Transfixion','Detonation'}, 66 | Liquefaction = {'Scission','Impaction'}, Scission = {'Liquefaction','Reverberation','Detonation'}, 67 | Reverberation = {'Induration','Impaction'}, Detonation = {'Compression','Scission'}, 68 | Induration = {'Compression','Reverberation','Impaction'}, Impaction = {'Liquefaction','Detonation'}, 69 | Fusion = {'Gravitation','Fragmentation'}, Fragmentation = {'Distortion','Fusion'}, 70 | Distortion = {'Fusion','Gravitation'}, Gravitation = {'Fragmentation','Distortion'}, 71 | Light = {'Light','Transfixion','Liquefaction','Detonation','Impaction','Fusion','Fragmentation'}, 72 | Darkness = {'Darkness','Compression','Induration','Scission','Reverberation','Distortion','Gravitation'} 73 | } 74 | 75 | windower.register_event('incoming text',function (original) 76 | local caught = 0 77 | if original:contains('Accomplished Adventurer uses') then 78 | caught = 32 79 | elseif original:contains('Rolandienne uses') then 80 | caught = 20 81 | elseif original:contains('Urbiolaine uses') then 82 | caught = 19 83 | elseif original:contains('Fablinix uses') then 84 | caught = 17 85 | end 86 | 87 | local used = '' 88 | if caught > 0 then 89 | used = original:sub(caught, #original-1) 90 | for wtype,wses in pairs(props) do 91 | if wses[used] then 92 | last_weap_type = wtype 93 | end 94 | end 95 | end 96 | 97 | local now = os.clock() 98 | local used_props = S{} 99 | 100 | if original:contains('Skillchain Level') and ((now - last) > 2) then 101 | local words = original:split(' ') 102 | local sc = words[4] 103 | sc = sc:sub(1, #sc-2) 104 | used_props:add(sc) 105 | last = now 106 | elseif original:contains('Accomplished Adventurer uses') and (last_weap_type == 'gaxe') then 107 | local wsprops = props.gaxe[used] 108 | if wsprops then 109 | used_props = wsprops 110 | end 111 | elseif original:contains('Good Call') then 112 | last_weap_type = next_weap(last_weap_type) 113 | used_props = last_scs 114 | elseif original:contains('Bad call!') then 115 | last_weap_type = next_weap(last_weap_type) 116 | local wsprops = props[last_weap_type][used] 117 | if wsprops then 118 | used_props = wsprops 119 | end 120 | end 121 | 122 | if not used_props:empty() then 123 | last_scs = used_props 124 | local want = {} 125 | for wsprop,_ in pairs(used_props) do 126 | local need = need_next[wsprop] 127 | for _,prop in pairs(need) do 128 | for weap,wses in pairs(props) do 129 | for ws,ps in pairs(wses) do 130 | for _,p in pairs(need) do 131 | if ps:contains(p) then 132 | if not want[weap] then 133 | want[weap] = S{} 134 | end 135 | want[weap]:add(ws) 136 | end 137 | end 138 | end 139 | end 140 | end 141 | 142 | end 143 | 144 | local w = next_weap(last_weap_type) 145 | atc(215, tostring(want[w])) 146 | end 147 | end) 148 | 149 | function atc(c, msg) 150 | if (type(c) == 'string') and (msg == nil) then 151 | msg = c 152 | c = 0 153 | end 154 | windower.add_to_chat(c, msg) 155 | end -------------------------------------------------------------------------------- /eventScripts/data/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | true 7 | true 8 | true 9 | true 10 | 11 | 12 | <1>wait 5; input /chatmode party 13 | <2>du bmn self always off 14 | 15 | 16 | <3> 17 | /pcmd add {sender} 18 | 19 | 20 | 21 | 22 | <3>du bmn self always on 23 | 24 | 25 | 26 | 27 | true 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /eventScripts/eventScripts.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'eventScripts' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'eventScripts', 'escripts'} 4 | _addon.version = '0.3.2' 5 | _addon.lastUpdate = '2018.05.19' 6 | 7 | --[[ 8 | EventScripts is a Windower addon for FFXI that is designed to provide both 9 | general and character-specific logon script functionality. Additionally, it 10 | allows some level of event-based execution of commands like the old plugin 11 | AutoExec. The included example of this feature is to send a party invite to 12 | a player after receiving a specific message in a tell from them. 13 | --]] 14 | 15 | local config = require('config') 16 | 17 | local default_settings = { 18 | ['login_cmds'] = {'wait 5; input /chatmode party'}, 19 | ['addons'] = {['info']=true,['infoboxes']=true,['jponry']=true}, 20 | ['reactions'] = {[3] = {['invitemenow_please'] = '/pcmd add {sender}'}} 21 | } 22 | local settings = config.load(default_settings) 23 | local pname = nil 24 | local loaded_addons = {} 25 | 26 | windower.register_event('chat message', function(message, sender, mode, gm) 27 | local lmessage = message:lower() 28 | local reactions = settings.reactions[mode] 29 | if reactions == nil then return end 30 | local modMessage = lmessage:gsub(' ', '_') 31 | local reaction = reactions[modMessage] 32 | if reaction ~= nil then 33 | windower.send_command('input '..reaction:gsub('{sender}', sender)) 34 | elseif lmessage:contains('please') then 35 | local words = modMessage:split('_') 36 | end 37 | end) 38 | 39 | windower.register_event('addon command', function(command,...) 40 | if pname == nil then return end 41 | command = command and command:lower() or 'help' 42 | local args = {...} 43 | 44 | if command == 'reload' then 45 | windower.send_command('lua reload '.._addon.name) 46 | elseif command == 'unload' then 47 | windower.send_command('lua unload '.._addon.name) 48 | elseif command == 'addon' then 49 | changeAddons(args) 50 | else 51 | addonPrint('Error: Unknown command') 52 | end 53 | end) 54 | 55 | function changeAddons(args) 56 | local cmd = args[1]:lower() 57 | local addon = args[2]:lower() 58 | 59 | if cmd == 'add' then 60 | settings[pname].addons[addon] = true 61 | elseif cmd == 'remove' then 62 | settings[pname].addons[addon] = false 63 | else 64 | settings[pname].addons[cmd] = true 65 | end 66 | settings:save('all') 67 | end 68 | 69 | windower.register_event('login', 'load', function() 70 | local player = windower.ffxi.get_player() 71 | if player == nil then 72 | windower.send_command("config FrameRateDivisor 1") 73 | return 74 | end 75 | pname = player.name:lower() 76 | 77 | if not settings[pname] then 78 | settings[pname] = {login_cmds = {}, addons = {}, reactions = {}} 79 | end 80 | 81 | local login_cmds = merge(settings.login_cmds, settings[pname].login_cmds) 82 | for _,cmd in pairs(login_cmds) do 83 | windower.send_command(cmd) 84 | end 85 | 86 | local addons = merge(settings.addons, settings[pname].addons) 87 | for addon, should_load in pairs(addons) do 88 | if should_load then 89 | if (loaded_addons[addon] == nil) or (not loaded_addons[addon]) then 90 | windower.send_command('lua load '..addon) 91 | loaded_addons[addon] = true 92 | end 93 | else 94 | if (loaded_addons[addon] == nil) or loaded_addons[addon] then 95 | windower.send_command('lua unload '..addon) 96 | loaded_addons[addon] = false 97 | end 98 | end 99 | end 100 | end) 101 | 102 | windower.register_event('logout', function() 103 | pname = nil 104 | windower.send_command("config FrameRateDivisor 1") 105 | end) 106 | 107 | function merge(...) 108 | local args = {...} 109 | local newtab = {} 110 | for _,t in pairs(args) do 111 | for k,v in pairs(t) do newtab[k] = v end 112 | end 113 | return newtab 114 | end 115 | 116 | ----------------------------------------------------------------------------------------------------------- 117 | --[[ 118 | Copyright © 2016, Lorand 119 | All rights reserved. 120 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 121 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 122 | * 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. 123 | * Neither the name of eventScripts nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 124 | 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 Lorand 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. 125 | --]] 126 | ----------------------------------------------------------------------------------------------------------- 127 | -------------------------------------------------------------------------------- /infoBoxes/infoBox.lua: -------------------------------------------------------------------------------- 1 | local texts = require('texts') 2 | 3 | local InfoBox = {} 4 | 5 | function InfoBox.new(settings, label) 6 | local self = {} 7 | self.settings = settings 8 | self.text = texts.new(self.settings) 9 | self.label = label 10 | return setmetatable(self, {__index = InfoBox, __class = 'InfoBox'}) 11 | end 12 | 13 | function InfoBox:getPos() 14 | return self.text:pos() 15 | end 16 | 17 | function InfoBox:setPos(posx, posy) 18 | self.text:pos(posx, posy) 19 | end 20 | 21 | function InfoBox:refresh() 22 | if self.visible then 23 | self.text:show() 24 | else 25 | self.text:hide() 26 | end 27 | end 28 | 29 | function InfoBox:show() 30 | self.visible = true 31 | self:refresh() 32 | end 33 | 34 | function InfoBox:hide() 35 | self.visible = false 36 | self:refresh() 37 | end 38 | 39 | function InfoBox:updateContents(contents) 40 | self.contents = contents 41 | if self.label ~= nil then 42 | self.text:text(self.label..': '..self.contents) 43 | else 44 | self.text:text(self.contents) 45 | end 46 | self:show() 47 | end 48 | 49 | function InfoBox:destroy() 50 | self.text:destroy() 51 | end 52 | 53 | return InfoBox -------------------------------------------------------------------------------- /infoBoxes/infoBoxes.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'infoBoxes' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'infoBoxes', 'ib'} 4 | _addon.version = '1.1.3' 5 | _addon.lastUpdate = '2016.10.01' 6 | 7 | require('lor/lor_utils') 8 | _libs.lor.include_addon_name = true 9 | _libs.lor.req('all') 10 | 11 | require('sets') 12 | require('actions') 13 | local res = require('resources') 14 | local InfoBox = require('infoBox') 15 | local start_time = os.time() 16 | local strat_charge_time = {[1]=240,[2]=120,[3]=80,[4]=60,[5]=48} 17 | 18 | local boxSettings = {} 19 | boxSettings.stratagems = {pos = {x = -300, y = -20}, flags = {bottom = true, right = true}} 20 | boxSettings.target = {pos = {x = -125, y = 250}, flags = {bottom = false, right = true}} 21 | boxSettings.targHp = {pos = {x = -125, y = 270}, flags = {bottom = false, right = true}} 22 | boxSettings.acc = {pos = {x = -125, y = -20}, flags = {bottom = true, right = true}} 23 | boxSettings.speed = {pos = {x = -60, y = -20}, flags = {bottom = true, right = true}} 24 | boxSettings.dist = {pos = {x = -178, y = 21}, text = {font='Arial', size = 14}, flags = {right = true}} 25 | boxSettings.zt = {pos = {x = -100, y = 0}, text = {font='Arial', size = 12}, flags = {right = true}} 26 | boxSettings.mobHp = {pos={x=400,y=0}} 27 | boxSettings.track = {pos={x=800,y=0}} 28 | 29 | local boxes = {} 30 | local player 31 | local acc = {hits = 0, misses = 0} 32 | local track = T{} 33 | 34 | windower.register_event('load', 'login', function() 35 | player = windower.ffxi.get_player() 36 | boxes.stratagems = InfoBox.new(boxSettings.stratagems, 'Stratagems') 37 | boxes.target = InfoBox.new(boxSettings.target) 38 | boxes.targHp = InfoBox.new(boxSettings.targHp, 'HP') 39 | boxes.acc = InfoBox.new(boxSettings.acc, 'Acc') 40 | boxes.speed = InfoBox.new(boxSettings.speed) 41 | boxes.dist = InfoBox.new(boxSettings.dist) 42 | boxes.zt = InfoBox.new(boxSettings.zt) 43 | boxes.mobHp = InfoBox.new(boxSettings.mobHp) 44 | boxes.track = T{} 45 | end) 46 | 47 | windower.register_event('logout', function() 48 | player = nil 49 | end) 50 | 51 | windower.register_event('zone change', function(new_zone, old_zone) 52 | start_time = os.time() 53 | player = windower.ffxi.get_player() 54 | end) 55 | 56 | windower.register_event('addon command', function(command,...) 57 | command = command and command:lower() or 'help' 58 | local args = {...} 59 | 60 | if command == 'reload' then 61 | windower.send_command('lua unload '.._addon.name..'; lua load '.._addon.name) 62 | elseif command == 'unload' then 63 | windower.send_command('lua unload '.._addon.name) 64 | elseif command == 'reset' then 65 | acc.hits = 0 66 | acc.misses = 0 67 | elseif command == 'track' then 68 | track_mob(args[1]) 69 | elseif command == 'untrack' then 70 | if args[1] == nil then 71 | atc(123, 'Missing argument for untrack: index number') 72 | elseif not isnum(args[1]) then 73 | untrack_mob(tonumber(args[1])) 74 | else 75 | atc(123, 'Invalid arg for untrack') 76 | end 77 | else 78 | windower.add_to_chat(0, 'Error: Unable to parse valid command') 79 | end 80 | end) 81 | 82 | 83 | function untrack_mob(idx) 84 | if track[idx] ~= nil then 85 | track:remove(idx) 86 | boxes.track[idx]:hide() 87 | boxes.track:remove(idx) 88 | while idx <= #boxes.track do 89 | local x,y = boxes.track[idx]:getPos() 90 | if y > 0 then 91 | boxes.track[idx]:setPos(x,y-16) 92 | end 93 | idx = idx + 1 94 | end 95 | end 96 | end 97 | 98 | 99 | function track_mob(id) 100 | local mob_id 101 | if id ~= nil then 102 | mob_id = tonumber(id) 103 | else 104 | local mob = windower.ffxi.get_mob_by_target() 105 | if mob ~= nil then 106 | mob_id = mob.id 107 | end 108 | end 109 | 110 | if mob_id ~= nil then 111 | for _,cfg in pairs(track) do 112 | if cfg.id == mob_id then 113 | atc(123,'Already tracking that mob!') 114 | return 115 | end 116 | end 117 | track:append({id = mob_id, tod = nil}) 118 | local y_pos = boxSettings.track.pos.y - 16 119 | if #boxes.track > 0 then 120 | _,y_pos = boxes.track[#boxes.track]:getPos() 121 | end 122 | boxes.track:append(InfoBox.new({pos={x=boxSettings.track.pos.x,y=y_pos + 16}})) 123 | atcfs('Now tracking %s', mob_id) 124 | end 125 | end 126 | 127 | 128 | windower.register_event('prerender', function() 129 | if player then 130 | local now = os.time() 131 | boxes.zt:updateContents(os.date('!%H:%M:%S', now-start_time)) 132 | 133 | local me = windower.ffxi.get_mob_by_target('me') 134 | if me then 135 | boxes.speed:updateContents(('%+.0f %%'):format(100*((me.movement_speed/5)-1))) 136 | else 137 | boxes.speed:hide() 138 | end 139 | 140 | if S{player.main_job, player.sub_job}:contains('SCH') then 141 | boxes.stratagems:updateContents(get_available_stratagem_count()) 142 | else 143 | boxes.stratagems:hide() 144 | end 145 | 146 | local target = windower.ffxi.get_mob_by_target() 147 | --local tindex = windower.ffxi.get_player().target_index 148 | --target = target or (tindex and windower.ffxi.get_mob_by_index(tindex)) 149 | if target ~= nil then 150 | local target_t = windower.ffxi.get_mob_by_index(target.target_index) 151 | if target_t ~= nil then 152 | boxes.target:updateContents(target.name..' → '..target_t.name) 153 | else 154 | boxes.target:updateContents(target.name) 155 | end 156 | boxes.targHp:updateContents(target.hpp..'%') 157 | boxes.dist:updateContents(('%.1f'):format(target.distance:sqrt())) 158 | 159 | if (target.is_npc) then 160 | lastTarget = target.id 161 | end 162 | else 163 | boxes.target:hide() 164 | boxes.targHp:hide() 165 | boxes.dist:hide() 166 | end 167 | 168 | for tidx,_track in pairs(track) do 169 | if _track.id ~= nil then 170 | local tracked = windower.ffxi.get_mob_by_id(_track.id) 171 | if tracked ~= nil then 172 | if tracked.hpp > 0 then 173 | boxes.track[tidx]:updateContents(('%s is UP!'):format(tracked.name)) 174 | _track.tod = nil 175 | elseif _track.tod == nil then 176 | _track.tod = now 177 | else 178 | boxes.track[tidx]:updateContents(('%s %s'):format(os.date('!%H:%M:%S', now - _track.tod), tracked.name)) 179 | end 180 | end 181 | end 182 | end 183 | 184 | if (lastTarget ~= nil) then 185 | local lastMob = windower.ffxi.get_mob_by_id(lastTarget) 186 | if (lastMob ~= nil) then 187 | boxes.mobHp:updateContents(lastMob.name..': '..lastMob.hpp..'%') 188 | end 189 | else 190 | boxes.mobHp:hide() 191 | end 192 | 193 | if (acc.hits ~= 0) or (acc.misses ~= 0) then 194 | boxes.acc:updateContents(calcAcc()) 195 | else 196 | boxes.acc:hide() 197 | end 198 | else 199 | for _,box in pairs(boxes) do 200 | if class(box) ~= 'InfoBox' then 201 | for _,sbox in pairs(box) do 202 | box:hide() 203 | end 204 | else 205 | box:hide() 206 | end 207 | end 208 | end 209 | end) 210 | 211 | 212 | windower.register_event('action', function(_raw) 213 | local action = Action(_raw.category, _raw.param, _raw) 214 | if action ~= nil then 215 | if (action.raw.actor_id == player.id) and (action.raw.category == 'melee') then 216 | for _,target in pairs(action.raw.targets) do 217 | for _,subaction in pairs(target.actions) do 218 | if subaction.message == 1 or subaction.message == 67 then 219 | acc.hits = acc.hits + 1 220 | elseif subaction.message == 15 or subaction.message == 63 then 221 | acc.misses = acc.misses + 1 222 | end 223 | end 224 | end 225 | end 226 | end 227 | end) 228 | 229 | function calcAcc() 230 | local swings = acc.hits + acc.misses 231 | if swings == 0 then return '0%' end 232 | local pct = acc.hits / swings 233 | pct = math.floor(pct*10000)/100 234 | return tostring(pct)..'%' 235 | end 236 | 237 | --[[ 238 | Calculates and returns the maximum number of SCH stratagems available for use. 239 | --]] 240 | function get_max_stratagem_count() 241 | if S{player.main_job, player.sub_job}:contains('SCH') then 242 | local lvl = player.main_job == 'SCH' and player.main_job_level or player.sub_job_level 243 | return math.floor(((lvl - 10) / 20) + 1) 244 | else 245 | return 0 246 | end 247 | end 248 | 249 | --[[ 250 | Calculates the number of SCH stratagems that are currently available for use. Calculated from the combined recast timer for stratagems and the maximum number 251 | of stratagems that are available. The recast time for each stratagem charge corresponds directly with the maximum number of stratagems that can be used. 252 | --]] 253 | function get_available_stratagem_count() 254 | local recastTime = windower.ffxi.get_ability_recasts()[231] or 0 255 | local maxStrats = get_max_stratagem_count() 256 | if maxStrats == 0 then return 0 end 257 | local stratsUsed = (recastTime/strat_charge_time[maxStrats]):ceil() 258 | return maxStrats - stratsUsed 259 | end 260 | 261 | ----------------------------------------------------------------------------------------------------------- 262 | --[[ 263 | Copyright © 2016, Lorand 264 | All rights reserved. 265 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 266 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 267 | * 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. 268 | * Neither the name of info nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 269 | 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 Lorand 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. 270 | --]] 271 | ----------------------------------------------------------------------------------------------------------- 272 | -------------------------------------------------------------------------------- /jponry/jponry.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'jponry' 2 | _addon.author = 'Lorand' 3 | _addon.command = 'jponry' 4 | _addon.version = '1.4' 5 | 6 | local chars = require('chat.chars') 7 | require('luau') 8 | 9 | local charsb = {['j^'] = string.char(129, 79)} 10 | 11 | local rchars = { 12 | ['rf'] = string.char(132, 112), ['r,'] = string.char(132, 113), ['rd'] = string.char(132, 114), ['ru'] = string.char(132, 115), ['rl'] = string.char(132, 116), 13 | ['rt'] = string.char(132, 117), ['r\\'] = string.char(132, 118),['r;'] = string.char(132, 119), ['rp'] = string.char(132, 120), ['rb'] = string.char(132, 121), 14 | ['rq'] = string.char(132, 122), ['rr'] = string.char(132, 123), ['rk'] = string.char(132, 124), ['rv'] = string.char(132, 125), ['ry'] = string.char(132, 126), 15 | ['rj'] = string.char(132, 127), ['rg'] = string.char(132, 129), ['rh'] = string.char(132, 130), ['rc'] = string.char(132, 131), ['rn'] = string.char(132, 132), 16 | ['re'] = string.char(132, 133), ['ra'] = string.char(132, 134), ['r['] = string.char(132, 135), ['rw'] = string.char(132, 136), ['rx'] = string.char(132, 137), 17 | ['ri'] = string.char(132, 138), ['ro'] = string.char(132, 139), ['r]'] = string.char(132, 140), ['rs'] = string.char(132, 141), ['rm'] = string.char(132, 142), 18 | ['r\''] = string.char(132, 143),['r.'] = string.char(132, 144), ['rz'] = string.char(132, 145), 19 | ['rF'] = string.char(132, 64), ['r<'] = string.char(132, 65), ['rD'] = string.char(132, 66), ['rU'] = string.char(132, 67), ['rL'] = string.char(132, 68), 20 | ['rT'] = string.char(132, 69), ['r|'] = string.char(132, 70), ['r:'] = string.char(132, 71), ['rP'] = string.char(132, 72), ['rB'] = string.char(132, 73), 21 | ['rQ'] = string.char(132, 74), ['rR'] = string.char(132, 75), ['rK'] = string.char(132, 76), ['rV'] = string.char(132, 77), ['rY'] = string.char(132, 78), 22 | ['rJ'] = string.char(132, 79), ['rG'] = string.char(132, 80), ['rH'] = string.char(132, 81), ['rC'] = string.char(132, 82), ['rN'] = string.char(132, 83), 23 | ['rE'] = string.char(132, 84), ['rA'] = string.char(132, 85), ['r{'] = string.char(132, 86), ['rW'] = string.char(132, 87), ['rX'] = string.char(132, 88), 24 | ['rI'] = string.char(132, 89), ['rO'] = string.char(132, 90), ['r}'] = string.char(132, 91), ['rS'] = string.char(132, 92), ['rM'] = string.char(132, 93), 25 | ['r"'] = string.char(132, 94), ['r>'] = string.char(132, 95), ['rZ'] = string.char(132, 96), 26 | } 27 | 28 | windower.register_event('addon command', function (command,...) 29 | command = command or 'help' 30 | local args = {...} 31 | 32 | if command:lower() == 'reload' then 33 | windower.send_command('lua reload '.._addon.name) 34 | elseif command:lower() == 'unload' then 35 | windower.send_command('lua unload '.._addon.name) 36 | elseif command:lower() == 'jponry' then 37 | convert({'JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY','JP','ONRY'},'j',args[1]) 38 | elseif command:lower() == 'chars' then 39 | for k,v in pairs(chars) do 40 | windower.add_to_chat(1, v..' : '..k) 41 | end 42 | elseif command:lower() == 'char' then 43 | printChar(args) 44 | elseif command:lower() == 'printchars' then 45 | printChars(args) 46 | elseif command:lower() == 'r' then 47 | convert(args, 'r') 48 | else 49 | convert(args, 'j', command) 50 | end 51 | end) 52 | 53 | windower.register_event('outgoing text', function(_, modified) 54 | if (modified:contains('')) then 55 | local targ = windower.ffxi.get_mob_by_target() 56 | if (targ ~= nil) then 57 | local reptxt = tostring(targ.hpp)..'%':escape() 58 | return modified:gsub('', reptxt) 59 | end 60 | end 61 | end) 62 | 63 | function convert(words, mode, chatmode) 64 | local w1 = 1 65 | if chatmode == nil then 66 | chatmode = words[1] 67 | w1 = 2 68 | end 69 | if chatmode == '/t' then 70 | chatmode = '/t '..words[w1] 71 | w1 = w1 + 1 72 | end 73 | 74 | local charmap = merge(chars, charsb, rchars) 75 | 76 | local text = '' 77 | for i = w1, #words, 1 do 78 | for c in words[i]:gmatch('.') do 79 | if charmap[mode..c] ~= nil then 80 | text = text..charmap[mode..c] 81 | else 82 | text = text..c 83 | end 84 | end 85 | if i < #words then 86 | text = text..' ' 87 | end 88 | end 89 | if text ~= '' then 90 | windower.send_command('input '..chatmode..' '..text) 91 | end 92 | end 93 | 94 | function merge(...) 95 | local args = {...} 96 | local newtab = {} 97 | for _,t in pairs(args) do 98 | for k,v in pairs(t) do newtab[k] = v end 99 | end 100 | return newtab 101 | end 102 | 103 | function printChar(args) 104 | local a = tonumber(args[1]) 105 | local b = tonumber(args[2]) 106 | local c = string.char(a, b) 107 | 108 | if c ~= nil then 109 | windower.add_to_chat(1, c..' = string.char('..a..', '..b..')') 110 | else 111 | windower.add_to_chat(1, 'Invalid character code: ('..a..', '..b..')') 112 | end 113 | end 114 | 115 | function printChars(args) 116 | local a = tonumber(args[1]) 117 | 118 | for i = 1, 255, 1 do 119 | local c = string.char(a, i) 120 | if c ~= nil then 121 | windower.add_to_chat(1, c..' = string.char('..a..', '..i..')') 122 | end 123 | end 124 | end 125 | 126 | ----------------------------------------------------------------------------------------------------------- 127 | --[[ 128 | Copyright © 2014, Lorand 129 | All rights reserved. 130 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 131 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 132 | * 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. 133 | * Neither the name of ffxiHealer nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 134 | 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 Lorand 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. 135 | --]] 136 | ----------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /shortcuts/data/aliases.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Teleport-Altep 5 | Fowl Aubade 6 | Mage's Ballad 7 | Mage's Ballad 8 | Mage's Ballad II 9 | Mage's Ballad III 10 | Blink 11 | Cure 12 | Cure II 13 | Cure III 14 | Cure IV 15 | Cure V 16 | Cure VI 17 | Gold Capriccio 18 | Celerity 19 | Curaga 20 | Curaga II 21 | Curaga III 22 | Curaga IV 23 | Curaga V 24 | Curaga VI 25 | Curing Waltz 26 | Curing Waltz II 27 | Curing Waltz III 28 | Curing Waltz IV 29 | Curing Waltz V 30 | Warp II 31 | Retrace 32 | Dark Arts 33 | Teleport-Dem 34 | Divine Seal 35 | Elemental Seal 36 | Shining Fantasia 37 | Goblin Gavotte 38 | Gravity 39 | Teleport-Holla 40 | Healing Waltz 41 | Goddess's Hymnus 42 | Utsusemi: Ichi 43 | Invisible 44 | Recall-Jugner 45 | Light Arts 46 | Sword Madrigal 47 | Blade Madrigal 48 | Sword Madrigal 49 | Blade Madrigal 50 | Sheepfoe Mambo 51 | Dragonfoe Mambo 52 | Advancing March 53 | Victory March 54 | Raptor Mazurka 55 | Chocobo Mazurka 56 | Teleport-Mea 57 | Recall-Meriph 58 | Recall-Meriph 59 | Valor Minuet 60 | Valor Minuet 61 | Valor Minuet II 62 | Valor Minuet III 63 | Valor Minuet IV 64 | Valor Minuet V 65 | Knight's Minne 66 | Knight's Minne 67 | Knight's Minne II 68 | Knight's Minne III 69 | Knight's Minne IV 70 | Knight's Minne V 71 | Valor Minuet 72 | Valor Minuet 73 | Valor Minuet II 74 | Valor Minuet III 75 | Valor Minuet IV 76 | Valor Minuet V 77 | Utsusemi: Ni 78 | Scop's Operetta 79 | Puppet's Operetta 80 | Protect 81 | Protect II 82 | Protect III 83 | Protect IV 84 | Protect V 85 | Army's Paeon 86 | Army's Paeon 87 | Army's Paeon II 88 | Army's Paeon III 89 | Army's Paeon IV 90 | Army's Paeon V 91 | Army's Paeon VI 92 | Paralyze 93 | Paralyze II 94 | Recall-Pashh 95 | Recall-Pashh 96 | Herb Pastoral 97 | Penury 98 | Protectra 99 | Protectra II 100 | Protectra III 101 | Protectra IV 102 | Protectra V 103 | Hunter's Prelude 104 | Archer's Prelude 105 | Protectra 106 | Protectra II 107 | Protectra III 108 | Protectra IV 109 | Protectra V 110 | Raise 111 | Raise II 112 | Raise III 113 | Regen 114 | Regen II 115 | Regen III 116 | Regen IV 117 | Regen V 118 | Warding Round 119 | Reraise 120 | Reraise 121 | Reraise II 122 | Reraise III 123 | Reraise IV 124 | Shell 125 | Shell II 126 | Shell III 127 | Shell IV 128 | Shell V 129 | Sentinel's Scherzo 130 | Shellra 131 | Shellra II 132 | Shellra III 133 | Shellra IV 134 | Shellra V 135 | Shellra 136 | Shellra II 137 | Shellra III 138 | Shellra IV 139 | Shellra V 140 | Shellra 141 | Shellra II 142 | Shellra III 143 | Shellra IV 144 | Shellra V 145 | Stoneskin 146 | Utsusemi: Ichi 147 | Utsusemi: Ni 148 | Teleport-Vahzl 149 | Teleport-Yhoat 150 | 151 | 152 | -------------------------------------------------------------------------------- /spellSpammer/spellSpammer.lua: -------------------------------------------------------------------------------- 1 | _addon.name = 'spellSpammer' 2 | _addon.author = 'Lorand' 3 | _addon.commands = {'spam','spellSpammer'} 4 | _addon.version = '1.2.1' 5 | 6 | local res = require('resources') 7 | local config = require('config') 8 | local aliases = config.load('..\\shortcuts\\data\\aliases.xml') 9 | --local spellToSpam = 'Stone' 10 | local spellsToSpam = {'Indi-Poison','Indi-Voidance','Indi-Precision'} 11 | local lastIndex = 0 12 | local keepSpamming = false 13 | local spamDelay = 0.8 14 | local spammer = {name='',actionStart=0,actionEnd=0} 15 | 16 | local settings = {actionDelay = 2.75} 17 | 18 | 19 | windower.register_event('addon command', function (command,...) 20 | command = command and command:lower() or 'help' 21 | local args = {...} 22 | 23 | if command == 'reload' then 24 | windower.send_command('lua reload spellSpammer') 25 | elseif command == 'unload' then 26 | windower.send_command('lua unload spellSpammer') 27 | elseif S{'on','start'}:contains(command) then 28 | keepSpamming = true 29 | print_status() 30 | elseif S{'off','stop'}:contains(command) then 31 | keepSpamming = false 32 | print_status() 33 | elseif command == 'toggle' then 34 | keepSpamming = not keepSpamming 35 | print_status() 36 | elseif S{'use','cast'}:contains(command) then 37 | local arg_string = table.concat(args,' ') 38 | local spellName = formatSpellName(arg_string) 39 | local spell = res.spells:with('en', spellName) 40 | if (spell ~= nil) then 41 | if canCast(spell) then 42 | spellToSpam = spell.en 43 | atc(0,'Successfully changed spell to spam to: '..spell.en) 44 | else 45 | atc(123,'Error: Unable to cast '..spell.en) 46 | end 47 | else 48 | atc(123,'Error: Invalid spell name: '..arg_string..' | '..spellName) 49 | end 50 | elseif command == 'status' then 51 | print_status() 52 | else 53 | atc(123, 'Error: Unknown command') 54 | end 55 | end) 56 | 57 | windower.register_event('load', function() 58 | lastAttempt = os.clock() 59 | end) 60 | 61 | windower.register_event('prerender', function() 62 | if keepSpamming then 63 | local now = os.clock() 64 | if (now - lastAttempt) >= spamDelay then 65 | local player = windower.ffxi.get_player() 66 | local mob = windower.ffxi.get_mob_by_target() 67 | --local spell = res.spells:with('en', spellToSpam) 68 | local spell = get_spell() 69 | 70 | if (player ~= nil) and (player.status == 0) and (mob ~= nil) and (spell ~= nil) then 71 | spammer.name = player.name 72 | if (windower.ffxi.get_spell_recasts()[spell.recast_id] == 0) then 73 | if (player.vitals.mp >= spell.mp_cost) and (mob.hpp > 0) then 74 | windower.send_command('input '..spell.prefix..' "'..spell.en..'" ') 75 | --local add_delay = 1.8 76 | local add_delay = spell.cast_time + 2.5 77 | --if (spell.recast >= 4) then 78 | -- add_delay = 0.2 79 | --end 80 | --spamDelay = spell.recast + add_delay + (math.random(1, 3)/10) 81 | spamDelay = add_delay + (math.random(2, 9)/10) 82 | end 83 | end 84 | end 85 | lastAttempt = now 86 | end 87 | end 88 | end) 89 | 90 | --[[ 91 | windower.register_event('prerender', function() 92 | local now = os.clock() 93 | local acting = isPerformingAction(moving) 94 | local player = windower.ffxi.get_player() 95 | spammer.name = player and player.name or 'Player' 96 | if (player ~= nil) and S{0,1}:contains(player.status) then --0/1 = idle/engaged 97 | 98 | 99 | if keepSpamming and not acting then 100 | if (now - spammer.lastAction) > settings.actionDelay then 101 | 102 | spammer.lastAction = now --Refresh stored action check time 103 | end 104 | end 105 | 106 | end 107 | end) 108 | --]] 109 | 110 | 111 | function sizeof(tbl) 112 | local c = 0 113 | for _,_ in pairs(tbl) do c = c + 1 end 114 | return c 115 | end 116 | 117 | function get_spell() 118 | local index = lastIndex + 1 119 | if (index < 1) or (index > sizeof(spellsToSpam)) then 120 | index = 1 121 | end 122 | local spell_name = spellsToSpam[index] 123 | local spell = res.spells:with('en', spell_name) 124 | lastIndex = index 125 | return spell 126 | end 127 | 128 | function print_status() 129 | local onoff = keepSpamming and 'On' or 'Off' 130 | --local spellToSpam = get_spell() 131 | --windower.add_to_chat(0, '[spellSpammer: '..onoff..'] {'..spellToSpam..'}') 132 | windower.add_to_chat(0, '[spellSpammer: '..onoff..']') 133 | end 134 | 135 | function atc(c, msg) 136 | if (type(c) == 'string') and (msg == nil) then 137 | msg = c 138 | c = 0 139 | end 140 | windower.add_to_chat(c, '[spellSpammer]'..msg) 141 | end 142 | 143 | function canCast(spell) 144 | if spell.prefix == '/magic' then 145 | local player = windower.ffxi.get_player() 146 | if (player == nil) or (spell == nil) then return false end 147 | local mainCanCast = (spell.levels[player.main_job_id] ~= nil) and (spell.levels[player.main_job_id] <= player.main_job_level) 148 | local subCanCast = (spell.levels[player.sub_job_id] ~= nil) and (spell.levels[player.sub_job_id] <= player.sub_job_level) 149 | local spellAvailable = windower.ffxi.get_spells()[spell.id] 150 | return spellAvailable and (mainCanCast or subCanCast) 151 | end 152 | return true 153 | end 154 | 155 | dec2roman = {'I','II','III','IV','V','VI','VII','VIII','IX','X','XI'} 156 | roman2dec = {['I']=1,['II']=2,['III']=3,['IV']=4,['V']=5,['VI']=6,['VII']=7,['VIII']=8,['IX']=9,['X']=10,['XI']=11} 157 | 158 | function formatSpellName(text) 159 | if (type(text) ~= 'string') or (#text < 1) then return nil end 160 | 161 | if (aliases ~= nil) then 162 | local fromAlias = aliases[text] 163 | if (fromAlias ~= nil) then 164 | return fromAlias 165 | end 166 | end 167 | 168 | local parts = text:split(' ') 169 | if #parts >= 2 then 170 | local name = formatName(parts[1]) 171 | for p = 2, #parts do 172 | local part = parts[p] 173 | local tier = toRomanNumeral(part) or part:upper() 174 | if (roman2dec[tier] == nil) then 175 | name = name..' '..formatName(part) 176 | else 177 | name = name..' '..tier 178 | end 179 | end 180 | return name 181 | else 182 | local name = formatName(text) 183 | local tier = text:sub(-1) 184 | local rnTier = toRomanNumeral(tier) 185 | if (rnTier ~= nil) then 186 | return name:sub(1, #name-1)..' '..rnTier 187 | else 188 | return name 189 | end 190 | end 191 | end 192 | 193 | function formatName(text) 194 | if (text ~= nil) and (type(text) == 'string') then 195 | return text:lower():ucfirst() 196 | end 197 | return text 198 | end 199 | 200 | function toRomanNumeral(val) 201 | if type(val) ~= 'number' then 202 | if type(val) == 'string' then 203 | val = tonumber(val) 204 | else 205 | return nil 206 | end 207 | end 208 | return dec2roman[val] 209 | end 210 | 211 | 212 | 213 | function isPerformingAction(moving) 214 | if (os.clock() - spammer.actionStart) > 8 then 215 | --Precaution in case an action completion isn't registered for a long time 216 | spammer.actionEnd = os.clock() 217 | end 218 | 219 | local acting = (spammer.actionEnd < spammer.actionStart) 220 | 221 | if (lastActingState ~= acting) then --If the current acting state is different from the last one 222 | if lastActingState then --If an action was being performed 223 | settings.actionDelay = 2.75 --Set a longer delay 224 | spammer.lastAction = os.clock() --The delay will be from this time 225 | else --If no action was being performed 226 | settings.actionDelay = 0.1 --Set a short delay 227 | end 228 | lastActingState = acting --Refresh the last acting state 229 | end 230 | 231 | return acting 232 | end 233 | 234 | 235 | --[[ 236 | Analyze the data contained in incoming packets for useful info. 237 | @param id packet ID 238 | @param data raw packet contents 239 | --]] 240 | function handle_incoming_chunk(id, data) 241 | if S{0x28,0x29}:contains(id) then --Action / Action Message 242 | local ai = get_action_info(id, data) 243 | local actor = windower.ffxi.get_mob_by_id(ai.actor_id) 244 | if (actor == nil) then return end 245 | if id == 0x28 then 246 | for _,targ in pairs(ai.targets) do 247 | local target = windower.ffxi.get_mob_by_id(targ.id) 248 | if (target == nil) then return end 249 | for _,tact in pairs(targ.actions) do 250 | if spammer.name == actor.name then 251 | if messages_initiating:contains(tact.message_id) then 252 | spammer.actionStart = os.clock() 253 | elseif messages_completing:contains(tact.message_id) then 254 | spammer.actionEnd = os.clock() 255 | end 256 | end 257 | end 258 | end 259 | elseif id == 0x29 then 260 | if spammer.name == actor.name then 261 | if messages_initiating:contains(ai.message_id) then 262 | spammer.actionStart = os.clock() 263 | elseif messages_completing:contains(ai.message_id) then 264 | spammer.actionEnd = os.clock() 265 | end 266 | end 267 | end 268 | end 269 | end 270 | 271 | 272 | --[[ 273 | Parse the given packet and construct a table to make its contents useful. 274 | Based on the 'incoming chunk' function in the Battlemod addon (thanks to Byrth / SnickySnacks) 275 | @param id packet ID 276 | @param data raw packet contents 277 | @return a table representing the given packet's data 278 | --]] 279 | function get_action_info(id, data) 280 | local pref = data:sub(1,4) 281 | local data = data:sub(5) 282 | if id == 0x28 then -------------- ACTION PACKET --------------- 283 | local act = { 284 | actor_id = get_bit_packed(data,8,40), 285 | target_count = get_bit_packed(data,40,50), 286 | targets = {} 287 | } 288 | local offset = 118 289 | for i = 1, act.target_count do 290 | act.targets[i] = { 291 | id = get_bit_packed(data,offset,offset+32), 292 | action_count = get_bit_packed(data,offset+32,offset+36), 293 | actions = {} 294 | } 295 | offset = offset + 36 296 | for n = 1,act.targets[i].action_count do 297 | act.targets[i].actions[n] = { 298 | message_id = get_bit_packed(data,offset+44,offset+54), 299 | has_add_efct = get_bit_packed(data,offset+85,offset+86) 300 | } 301 | offset = offset + 86 302 | if act.targets[i].actions[n].has_add_efct == 1 then 303 | offset = offset + 37 304 | end 305 | act.targets[i].actions[n].has_spike_efct = get_bit_packed(data,offset,offset+1) 306 | offset = offset + 1 307 | if act.targets[i].actions[n].has_spike_efct == 1 then 308 | offset = offset + 34 309 | end 310 | end 311 | end 312 | return act 313 | elseif id == 0x29 then ----------- ACTION MESSAGE ------------ 314 | local am = {} 315 | am.actor_id = get_bit_packed(data,0,32) 316 | am.target_id = get_bit_packed(data,32,64) 317 | am.message_id = get_bit_packed(data,160,175) -- Cut off the most significant bit, hopefully 318 | return am 319 | end 320 | end 321 | 322 | function get_bit_packed(dat_string,start,stop) 323 | --Copied from Battlemod; thanks to Byrth / SnickySnacks 324 | local newval = 0 325 | local c_count = math.ceil(stop/8) 326 | while c_count >= math.ceil((start+1)/8) do 327 | local cur_val = dat_string:byte(c_count) 328 | local scal = 256 329 | if c_count == math.ceil(stop/8) then 330 | cur_val = cur_val%(2^((stop-1)%8+1)) 331 | end 332 | if c_count == math.ceil((start+1)/8) then 333 | cur_val = math.floor(cur_val/(2^(start%8))) 334 | scal = 2^(8-start%8) 335 | end 336 | newval = newval*scal + cur_val 337 | c_count = c_count - 1 338 | end 339 | return newval 340 | end 341 | 342 | 343 | ----------------------------------------------------------------------------------------------------------- 344 | --[[ 345 | Copyright © 2016, Lorand 346 | All rights reserved. 347 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 348 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 349 | * 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. 350 | * Neither the name of ffxiHealer nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 351 | 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 Lorand 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. 352 | --]] 353 | ----------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------